From 372f1eaa7e751fcbf25601aceacb37c0d17682c9 Mon Sep 17 00:00:00 2001 From: Sean Date: Fri, 11 Dec 2020 16:22:37 +0100 Subject: [PATCH] Support youtube as livestream URL Allows to use youtube as as live stream url config, will load the youtube embedded player in an iframe instead of video.js --- .../components/jitsi/jitsi.component.html | 15 +- .../components/jitsi/jitsi.component.ts | 73 ++++++- .../live-stream/live-stream.component.html | 29 --- .../live-stream/live-stream.component.scss | 40 ---- .../live-stream/live-stream.component.spec.ts | 27 --- .../live-stream/live-stream.component.ts | 43 ---- .../video-player/video-player.component.html | 35 +++ .../video-player.component.scss} | 32 ++- .../video-player.component.spec.ts} | 8 +- .../video-player/video-player.component.ts | 201 ++++++++++++++++++ .../vjs-player/vjs-player.component.html | 23 -- .../vjs-player/vjs-player.component.ts | 145 ------------- client/src/app/shared/shared.module.ts | 9 +- 13 files changed, 328 insertions(+), 352 deletions(-) delete mode 100644 client/src/app/shared/components/live-stream/live-stream.component.html delete mode 100644 client/src/app/shared/components/live-stream/live-stream.component.scss delete mode 100644 client/src/app/shared/components/live-stream/live-stream.component.spec.ts delete mode 100644 client/src/app/shared/components/live-stream/live-stream.component.ts create mode 100644 client/src/app/shared/components/video-player/video-player.component.html rename client/src/app/shared/components/{vjs-player/vjs-player.component.scss => video-player/video-player.component.scss} (70%) rename client/src/app/shared/components/{vjs-player/vjs-player.component.spec.ts => video-player/video-player.component.spec.ts} (69%) create mode 100644 client/src/app/shared/components/video-player/video-player.component.ts delete mode 100644 client/src/app/shared/components/vjs-player/vjs-player.component.html delete mode 100644 client/src/app/shared/components/vjs-player/vjs-player.component.ts diff --git a/client/src/app/shared/components/jitsi/jitsi.component.html b/client/src/app/shared/components/jitsi/jitsi.component.html index 534e6a1ab..768ab4eef 100644 --- a/client/src/app/shared/components/jitsi/jitsi.component.html +++ b/client/src/app/shared/components/jitsi/jitsi.component.html @@ -227,13 +227,14 @@ - -
+ + + +
diff --git a/client/src/app/shared/components/jitsi/jitsi.component.ts b/client/src/app/shared/components/jitsi/jitsi.component.ts index f5d58bea6..31940e3e8 100644 --- a/client/src/app/shared/components/jitsi/jitsi.component.ts +++ b/client/src/app/shared/components/jitsi/jitsi.component.ts @@ -1,5 +1,15 @@ import { animate, state, style, transition, trigger } from '@angular/animations'; -import { Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + HostListener, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation +} from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Title } from '@angular/platform-browser'; @@ -76,7 +86,8 @@ enum ConferenceState { transition('true <=> false', animate('1s')) ]) ], - encapsulation: ViewEncapsulation.None + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush }) export class JitsiComponent extends BaseViewComponentDirective implements OnInit, OnDestroy { public enableJitsi: boolean; @@ -116,7 +127,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit } public isJoined: boolean; - public streamRunning: boolean; + private streamRunning = false; private options: object; @@ -126,7 +137,13 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit // storage locks public isJitsiActiveInAnotherTab = false; - public streamActiveInAnotherTab = false; + + /** + * undefined is controlled behaviour, meaning, this property was not + * checked yet. + * Thus, false-checks have to be explicit + */ + private streamLoadedOnce: boolean; private RTC_LOGGED_STORAGE_KEY = 'rtcIsLoggedIn'; private STREAM_RUNNING_STORAGE_KEY = 'streamIsRunning'; @@ -271,7 +288,8 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit private configService: ConfigService, private closService: CurrentListOfSpeakersService, private userMediaPermService: UserMediaPermService, - private applauseService: ApplauseService + private applauseService: ApplauseService, + private cd: ChangeDetectorRef ) { super(titleService, translate, snackBar); } @@ -307,7 +325,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit private async stopConference(): Promise { await this.stopJitsi(); - if (this.streamActiveInAnotherTab && this.streamRunning) { + if (this.streamLoadedOnce && this.streamRunning) { await this.deleteStreamingLock(); } } @@ -322,6 +340,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit this.canManageSpeaker = this.operator.hasPerms(this.permission.agendaCanManageListOfSpeakers); this.canSeeLiveStream = this.operator.hasPerms(this.permission.coreCanSeeLiveStream); this.isEnterMeetingRoomVisible = this.canManageSpeaker; + this.cd.markForCheck(); }), this.storageMap @@ -332,13 +351,15 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit this.lockLoaded.resolve(); if (!inUse && !this.isJitsiActive) { this.startJitsi(); + this.cd.markForCheck(); } }), this.storageMap .watch(this.STREAM_RUNNING_STORAGE_KEY) .pipe(distinctUntilChanged()) .subscribe((running: boolean) => { - this.streamActiveInAnotherTab = running; + this.streamLoadedOnce = !!running; + this.cd.markForCheck(); }) ); @@ -393,6 +414,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit }), this.configService.get('general_system_applause_show_level').subscribe(show => { this.showApplauseLevel = show; + this.cd.markForCheck(); }), this.configService.get('general_system_applause_type').subscribe(type => { if (type === 'applause-type-bar') { @@ -415,9 +437,13 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit map(los => los?.findUserIndexOnList(this.operator.user.id) ?? -1), distinctUntilChanged() ) - .subscribe(userLosIndex => this.autoJoinJitsiByLosIndex(userLosIndex)), + .subscribe(userLosIndex => { + this.autoJoinJitsiByLosIndex(userLosIndex); + this.cd.markForCheck(); + }), this.applauseService.applauseLevelObservable.subscribe(applauseLevel => { this.applauseLevel = applauseLevel || 0; + this.cd.markForCheck(); }) ); } @@ -437,6 +463,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit private startJitsi(): void { if (!this.isJitsiActiveInAnotherTab && this.enableJitsi && !this.isJitsiActive && this.jitsiNode) { this.enterConferenceRoom(); + this.cd.markForCheck(); } } @@ -501,6 +528,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit if (this.videoStreamUrl) { this.showJitsiDialog(); } + this.cd.markForCheck(); } private autoJoinJitsiByLosIndex(operatorClosIndex: number): void { @@ -548,6 +576,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit id: newSpeakerId, displayName: this.members[newSpeakerId].name }; + this.cd.markForCheck(); } private addMember(newMember: JitsiMember): void { @@ -614,22 +643,42 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit public hideJitsiDialog(): void { this.isJitsiDialogOpen = false; + this.cd.markForCheck(); } public showJitsiDialog(): void { this.isJitsiDialogOpen = true; this.showJitsiWindow = false; + this.cd.markForCheck(); } public viewStream(): void { this.stopJitsi(); this.setConferenceState(ConferenceState.stream); this.showJitsiWindow = true; + this.cd.markForCheck(); } - public onSteamStarted(): void { - this.streamRunning = true; - this.storageMap.set(this.STREAM_RUNNING_STORAGE_KEY, true).subscribe(() => {}); + public onSteamLoaded(): void { + /** + * explicit false check, undefined would mean that this was not checked yet + */ + if (this.streamLoadedOnce === false) { + this.storageMap.set(this.STREAM_RUNNING_STORAGE_KEY, true).subscribe(() => { + this.streamRunning = true; + }); + } + } + + public showVideoPlayer(): boolean { + if (!this.canSeeLiveStream) { + return false; + } + return this.streamRunning || this.streamLoadedOnce === false; + } + + public isStreamInOtherTab(): boolean { + return !this.streamRunning && this.streamLoadedOnce; } public enterConferenceRoom(): void { @@ -654,6 +703,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit } else if (!this.videoStreamUrl && this.enableJitsi) { this.setConferenceState(ConferenceState.jitsi); } + this.cd.markForCheck(); } private async deleteJitsiLock(): Promise { @@ -673,6 +723,7 @@ export class JitsiComponent extends BaseViewComponentDirective implements OnInit this.applauseService.sendApplause(); setTimeout(() => { this.applauseDisabled = false; + this.cd.markForCheck(); }, this.applauseTimeout); } } diff --git a/client/src/app/shared/components/live-stream/live-stream.component.html b/client/src/app/shared/components/live-stream/live-stream.component.html deleted file mode 100644 index d2490506e..000000000 --- a/client/src/app/shared/components/live-stream/live-stream.component.html +++ /dev/null @@ -1,29 +0,0 @@ -
-
-
- -
- -
- - {{ 'The livestream is disabled because you are inside a conference' | translate }} - - -
-
- -
- -
-
diff --git a/client/src/app/shared/components/live-stream/live-stream.component.scss b/client/src/app/shared/components/live-stream/live-stream.component.scss deleted file mode 100644 index e1b007018..000000000 --- a/client/src/app/shared/components/live-stream/live-stream.component.scss +++ /dev/null @@ -1,40 +0,0 @@ -.stream-integration { - .stream-wrapper { - // position: absolute; - margin-right: 20px; - margin-bottom: 5px; - float: right; - } - - .user-in-conf-warning { - width: 300px; - height: 200px; - padding: 20px; - background-color: white; - - button { - display: block; - } - } - - .stream-bar { - margin-right: 20px; - // display: flex; - - margin-right: 20px; - $wrapper-padding: 5px; - $bar-height: 40px; - - .toggle-list-button { - height: 50px; - display: block; - margin-left: auto; - padding-right: 0.5em; - font-weight: normal; - text-align: right; - line-height: normal; - border-bottom-right-radius: 0; - border-bottom-left-radius: 0; - } - } -} diff --git a/client/src/app/shared/components/live-stream/live-stream.component.spec.ts b/client/src/app/shared/components/live-stream/live-stream.component.spec.ts deleted file mode 100644 index 0412aed72..000000000 --- a/client/src/app/shared/components/live-stream/live-stream.component.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { E2EImportsModule } from 'e2e-imports.module'; - -import { LiveStreamComponent } from './live-stream.component'; - -describe('LiveStreamComponent', () => { - let component: LiveStreamComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - imports: [E2EImportsModule], - declarations: [LiveStreamComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(LiveStreamComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/client/src/app/shared/components/live-stream/live-stream.component.ts b/client/src/app/shared/components/live-stream/live-stream.component.ts deleted file mode 100644 index 03892092c..000000000 --- a/client/src/app/shared/components/live-stream/live-stream.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -import { StorageMap } from '@ngx-pwa/local-storage'; -import { distinctUntilChanged } from 'rxjs/operators'; - -@Component({ - selector: 'os-live-stream', - templateUrl: './live-stream.component.html', - styleUrls: ['./live-stream.component.scss'] -}) -export class LiveStreamComponent implements OnInit { - public showStream = false; - - private RTC_LOGGED_STORAGE_KEY = 'rtcIsLoggedIn'; - - public isUserInConference: boolean; - - public constructor(private storageMap: StorageMap) {} - - public ngOnInit(): void { - this.storageMap - .watch(this.RTC_LOGGED_STORAGE_KEY) - .pipe(distinctUntilChanged()) - .subscribe((inUse: boolean) => { - this.isUserInConference = inUse; - }); - } - - public toggleShowStream(): void { - this.showStream = !this.showStream; - } - - public async forceReloadStream(): Promise { - await this.deleteJitsiLock(); - } - - /** - * todo: DUP - */ - private async deleteJitsiLock(): Promise { - await this.storageMap.delete(this.RTC_LOGGED_STORAGE_KEY).toPromise(); - } -} diff --git a/client/src/app/shared/components/video-player/video-player.component.html b/client/src/app/shared/components/video-player/video-player.component.html new file mode 100644 index 000000000..3b41595a6 --- /dev/null +++ b/client/src/app/shared/components/video-player/video-player.component.html @@ -0,0 +1,35 @@ +
+ +
+
+ +
+
+ +
+
+
+ +

+ {{ 'Currently no livestream available.' | translate }} +

+ +
+
+ + +
+
+
diff --git a/client/src/app/shared/components/vjs-player/vjs-player.component.scss b/client/src/app/shared/components/video-player/video-player.component.scss similarity index 70% rename from client/src/app/shared/components/vjs-player/vjs-player.component.scss rename to client/src/app/shared/components/video-player/video-player.component.scss index 74bb311a9..e845eccaa 100644 --- a/client/src/app/shared/components/vjs-player/vjs-player.component.scss +++ b/client/src/app/shared/components/video-player/video-player.component.scss @@ -16,11 +16,12 @@ .is-offline-wrapper { width: 100%; text-align: center; + width: 500px; + height: 200px; .offlineposter { position: relative; - width: 500px; - height: 200px; + img { max-width: 100%; max-height: 100%; @@ -40,25 +41,11 @@ } .player-container { - height: 100%; width: 100%; + .video-js { margin: auto; - // we keep the button for now - // .vjs-big-play-button { - // left: 0; - // top: 0; - // width: 100%; - // height: 100%; - // border: 0; - // border-radius: 0; - // background-color: rgba(0, 0, 0, 0); - // .vjs-icon-placeholder { - // display: none !important; - // } - // } - .vjs-control-bar { .vjs-subs-caps-button { display: none !important; @@ -73,5 +60,16 @@ } } } + + .youtube-player { + height: 100%; + width: 100%; + display: flex; + + .youtube-iFrame { + width: 100%; + height: 280px; + } + } } } diff --git a/client/src/app/shared/components/vjs-player/vjs-player.component.spec.ts b/client/src/app/shared/components/video-player/video-player.component.spec.ts similarity index 69% rename from client/src/app/shared/components/vjs-player/vjs-player.component.spec.ts rename to client/src/app/shared/components/video-player/video-player.component.spec.ts index 4eefdb1a4..af35100f7 100644 --- a/client/src/app/shared/components/vjs-player/vjs-player.component.spec.ts +++ b/client/src/app/shared/components/video-player/video-player.component.spec.ts @@ -2,11 +2,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { E2EImportsModule } from 'e2e-imports.module'; -import { VjsPlayerComponent } from './vjs-player.component'; +import { VideoPlayerComponent } from './video-player.component'; describe('VjsPlayerComponent', () => { - let component: VjsPlayerComponent; - let fixture: ComponentFixture; + let component: VideoPlayerComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -15,7 +15,7 @@ describe('VjsPlayerComponent', () => { })); beforeEach(() => { - fixture = TestBed.createComponent(VjsPlayerComponent); + fixture = TestBed.createComponent(VideoPlayerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/client/src/app/shared/components/video-player/video-player.component.ts b/client/src/app/shared/components/video-player/video-player.component.ts new file mode 100644 index 000000000..dc4cd9297 --- /dev/null +++ b/client/src/app/shared/components/video-player/video-player.component.ts @@ -0,0 +1,201 @@ +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + EventEmitter, + Input, + OnDestroy, + OnInit, + Output, + ViewChild, + ViewEncapsulation +} from '@angular/core'; + +import { of } from 'rxjs'; +import { ajax, AjaxResponse } from 'rxjs/ajax'; +import { catchError, map } from 'rxjs/operators'; +import videojs from 'video.js'; + +import { ConfigService } from 'app/core/ui-services/config.service'; + +enum MimeType { + mp4 = 'video/mp4', + mpd = 'application/dash+xml', + m3u8 = 'application/x-mpegURL' +} + +enum Player { + vjs, + youtube +} + +@Component({ + selector: 'os-video-player', + templateUrl: './video-player.component.html', + styleUrls: ['./video-player.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class VideoPlayerComponent implements OnDestroy, AfterViewInit { + @ViewChild('vjs', { static: false }) + private vjsPlayerElementRef: ElementRef; + + private _videoUrl: string; + + @Input() + public set videoUrl(value: string) { + this._videoUrl = value.trim(); + this.playerType = this.determinePlayer(this.videoUrl); + + if (this.usingVjs) { + this.mimeType = this.determineContentTypeByUrl(this.videoUrl); + this.initVjs(); + } else if (this.usingYouTube) { + this.stopVJS(); + this.unloadVjs(); + this.youTubeVideoId = this.getYouTubeVideoId(this.videoUrl); + } + } + + @Input() + public showParticles: boolean; + + public get videoUrl(): string { + return this._videoUrl; + } + + public posterUrl: string; + public vjsPlayer: videojs.Player; + public youTubeVideoId: string; + public isUrlOnline: boolean; + private playerType: Player; + private mimeType: MimeType; + + @Output() + private started: EventEmitter = new EventEmitter(); + + public get usingYouTube(): boolean { + return this.playerType === Player.youtube; + } + + public get usingVjs(): boolean { + return this.playerType === Player.vjs; + } + + public get youTubeVideoUrl(): string { + return `https://www.youtube.com/embed/${this.youTubeVideoId}?autoplay=1`; + } + + public constructor(config: ConfigService) { + config.get('general_system_stream_poster').subscribe(posterUrl => { + this.posterUrl = posterUrl?.trim(); + }); + } + + public ngAfterViewInit(): void { + this.started.next(); + } + + public ngOnDestroy(): void { + this.unloadVjs(); + } + + private stopVJS(): void { + if (this.vjsPlayer) { + this.vjsPlayer.src = ''; + this.vjsPlayer.pause(); + } + } + + private unloadVjs(): void { + if (this.vjsPlayer) { + this.vjsPlayer.dispose(); + this.vjsPlayer = null; + } + } + + private async isUrlReachable(): Promise { + /** + * Using observable would not make sense, because without it would not automatically update + * if a Ressource switches from online to offline + */ + const ajaxResponse: AjaxResponse = await ajax(this.videoUrl) + .pipe( + map(response => response), + catchError(error => { + return of(error); + }) + ) + .toPromise(); + + /** + * there is no enum for http status codes in the whole Angular stack... + */ + if (ajaxResponse.status === 200) { + this.isUrlOnline = true; + } else { + this.isUrlOnline = false; + } + } + + public async onRefreshVideo(): Promise { + await this.isUrlReachable(); + this.playVjsVideo(); + } + + private async initVjs(): Promise { + await this.isUrlReachable(); + + if (!this.vjsPlayer && this.usingVjs && this.vjsPlayerElementRef) { + this.vjsPlayer = videojs(this.vjsPlayerElementRef.nativeElement, { + textTrackSettings: false, + fluid: true, + autoplay: 'any', + liveui: true, + poster: this.posterUrl + }); + } + this.playVjsVideo(); + } + + private playVjsVideo(): void { + if (this.usingVjs && this.vjsPlayer && this.isUrlOnline) { + this.vjsPlayer.src({ + src: this.videoUrl, + type: this.mimeType + }); + } + } + + private determinePlayer(videoUrl: string): Player { + if (videoUrl.includes('youtu.be') || videoUrl.includes('youtube.')) { + return Player.youtube; + } else { + return Player.vjs; + } + } + + private getYouTubeVideoId(url: string): string { + const regExp = /^.*(youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/; + const match = url.match(regExp); + if (match && match[2].length === 11) { + return match[2]; + } + } + + private determineContentTypeByUrl(url: string): MimeType { + if (url) { + if (url.startsWith('rtmp')) { + throw new Error(`$rtmp (flash) streams cannot be supported`); + } else { + const extension = url?.split('.')?.pop(); + const mimeType = MimeType[extension]; + if (mimeType) { + return mimeType; + } else { + throw new Error(`${url} has an unknown mime type`); + } + } + } + } +} diff --git a/client/src/app/shared/components/vjs-player/vjs-player.component.html b/client/src/app/shared/components/vjs-player/vjs-player.component.html deleted file mode 100644 index b575a7dd6..000000000 --- a/client/src/app/shared/components/vjs-player/vjs-player.component.html +++ /dev/null @@ -1,23 +0,0 @@ -
- -
- -
-
- -

- {{ 'Currently no livestream available.' | translate }} -

- -
-
- - -
-
-
diff --git a/client/src/app/shared/components/vjs-player/vjs-player.component.ts b/client/src/app/shared/components/vjs-player/vjs-player.component.ts deleted file mode 100644 index 7f8bbf23c..000000000 --- a/client/src/app/shared/components/vjs-player/vjs-player.component.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { - Component, - ElementRef, - EventEmitter, - Input, - OnDestroy, - OnInit, - Output, - ViewChild, - ViewEncapsulation -} from '@angular/core'; - -import { of } from 'rxjs'; -import { ajax, AjaxResponse } from 'rxjs/ajax'; -import { catchError, map } from 'rxjs/operators'; -import videojs from 'video.js'; - -import { ConfigService } from 'app/core/ui-services/config.service'; - -interface VideoSource { - src: string; - type: MimeType; -} - -enum MimeType { - mp4 = 'video/mp4', - mpd = 'application/dash+xml', - m3u8 = 'application/x-mpegURL' -} - -@Component({ - selector: 'os-vjs-player', - templateUrl: './vjs-player.component.html', - styleUrls: ['./vjs-player.component.scss'], - encapsulation: ViewEncapsulation.None -}) -export class VjsPlayerComponent implements OnInit, OnDestroy { - @ViewChild('videoPlayer', { static: true }) - private videoPlayer: ElementRef; - private _videoUrl: string; - public posterUrl: string; - public player: videojs.Player; - public isUrlOnline: boolean; - - @Input() - public set videoUrl(value: string) { - this._videoUrl = value.trim(); - this.checkVideoUrl(); - } - - @Input() - public showParticles: boolean; - - @Output() - private started: EventEmitter = new EventEmitter(); - - public get videoUrl(): string { - return this._videoUrl; - } - - private get videoSource(): VideoSource { - return { - src: this.videoUrl, - type: this.determineContentTypeByUrl(this.videoUrl) - }; - } - - public constructor(config: ConfigService) { - config.get('general_system_stream_poster').subscribe(posterUrl => { - this.posterUrl = posterUrl?.trim(); - }); - } - - public async ngOnInit(): Promise { - this.initPlayer(); - } - - public ngOnDestroy(): void { - if (this.player) { - this.player.dispose(); - } - } - - public async checkVideoUrl(): Promise { - /** - * Using observable would not make sense, because without it would not automatically update - * if a Ressource switches from online to offline - */ - const ajaxResponse: AjaxResponse = await ajax(this.videoUrl) - .pipe( - map(response => response), - catchError(error => { - return of(error); - }) - ) - .toPromise(); - - /** - * there is no enum for http status codes in the whole Angular stack... - */ - if (ajaxResponse.status === 200) { - this.isUrlOnline = true; - this.playVideo(); - } else { - this.isUrlOnline = false; - if (this.player) { - this.player.pause(); - } - this.player.src(''); - } - } - - private initPlayer(): void { - if (!this.player) { - this.player = videojs(this.videoPlayer.nativeElement, { - textTrackSettings: false, - fluid: true, - autoplay: 'any', - liveui: true, - poster: this.posterUrl - }); - } - } - - private playVideo(): void { - this.player.src(this.videoSource); - this.started.next(); - } - - private determineContentTypeByUrl(url: string): MimeType { - if (url) { - if (url.startsWith('rtmp')) { - throw new Error(`$rtmp (flash) streams cannot be supported`); - } else { - const extension = url?.split('.')?.pop(); - const mimeType = MimeType[extension]; - if (mimeType) { - return mimeType; - } else { - throw new Error(`${url} has an unknown mime type`); - } - } - } - } -} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 0df08e294..d4b1248dc 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -127,8 +127,7 @@ import { AssignmentPollDetailContentComponent } from './components/assignment-po import { GlobalSpinnerComponent } from './components/global-spinner/global-spinner.component'; import { UserMenuComponent } from './components/user-menu/user-menu.component'; import { JitsiComponent } from './components/jitsi/jitsi.component'; -import { VjsPlayerComponent } from './components/vjs-player/vjs-player.component'; -import { LiveStreamComponent } from './components/live-stream/live-stream.component'; +import { VideoPlayerComponent } from './components/video-player/video-player.component'; import { ListOfSpeakersContentComponent } from './components/list-of-speakers-content/list-of-speakers-content.component'; import { ApplauseBarDisplayComponent } from './components/applause-bar-display/applause-bar-display.component'; import { ProgressComponent } from './components/progress/progress.component'; @@ -303,8 +302,7 @@ import { ApplauseParticleDisplayComponent } from './components/applause-particle MotionPollDetailContentComponent, AssignmentPollDetailContentComponent, JitsiComponent, - VjsPlayerComponent, - LiveStreamComponent, + VideoPlayerComponent, ListOfSpeakersContentComponent ], declarations: [ @@ -367,8 +365,7 @@ import { ApplauseParticleDisplayComponent } from './components/applause-particle MotionPollDetailContentComponent, AssignmentPollDetailContentComponent, JitsiComponent, - VjsPlayerComponent, - LiveStreamComponent, + VideoPlayerComponent, ListOfSpeakersContentComponent, ApplauseBarDisplayComponent, ProgressComponent,