Hide conference bar without interaction

Hide the conference bar if there is no stream and no entering
permission.

Hides the "see stream" button if use user has no permission to see the
stream (call list window)

Use rxjs combineLatest for easier "dead state" detection with less
change pushing
This commit is contained in:
Sean 2021-06-03 15:12:49 +02:00
parent 7dcdbb4ee1
commit 8b22f5ff0e
6 changed files with 78 additions and 116 deletions

View File

@ -72,15 +72,17 @@
</div> </div>
<div class="exit"> <div class="exit">
<!-- Exit jitsi, view stream --> <!-- Exit jitsi, view stream -->
<ng-container *osPerms="permission.coreCanSeeLiveStream">
<button <button
mat-icon-button mat-icon-button
color="primary" color="primary"
matTooltip="{{ 'Continue livestream' | translate }}" matTooltip="{{ 'Continue livestream' | translate }}"
(click)="viewStream()" (click)="viewStream()"
*ngIf="!!(liveStreamUrl | async)?.trim()" *ngIf="hasLiveStreamUrl | async"
> >
<mat-icon>live_tv</mat-icon> <mat-icon>live_tv</mat-icon>
</button> </button>
</ng-container>
</div> </div>
</div> </div>
</div> </div>

View File

@ -38,6 +38,8 @@ export class CallComponent extends BaseViewComponentDirective implements OnInit,
public isJitsiActiveInAnotherTab: Observable<boolean> = this.rtcService.inOtherTab; public isJitsiActiveInAnotherTab: Observable<boolean> = this.rtcService.inOtherTab;
public canEnterCall: Observable<boolean> = this.callRestrictionService.canEnterCallObservable; public canEnterCall: Observable<boolean> = this.callRestrictionService.canEnterCallObservable;
public isJitsiDialogOpen: Observable<boolean> = this.rtcService.showCallDialogObservable; public isJitsiDialogOpen: Observable<boolean> = this.rtcService.showCallDialogObservable;
public showParticles: Observable<boolean> = this.applauseService.showParticles;
public hasLiveStreamUrl: Observable<boolean> = this.streamService.hasLiveStreamUrlObvervable;
public isJitsiActive: boolean; public isJitsiActive: boolean;
public isJoined: boolean; public isJoined: boolean;
@ -64,18 +66,6 @@ export class CallComponent extends BaseViewComponentDirective implements OnInit,
return this.isJitsiActive && this.isJoined; return this.isJitsiActive && this.isJoined;
} }
public get showParticles(): Observable<boolean> {
return this.applauseService.showParticles;
}
public get canSeeLiveStream(): Observable<boolean> {
return this.streamService.canSeeLiveStreamObservable;
}
public get liveStreamUrl(): Observable<string> {
return this.streamService.liveStreamUrlObservable;
}
private autoConnect: boolean; private autoConnect: boolean;
@Output() @Output()

View File

@ -1,4 +1,4 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
@ -18,7 +18,7 @@ import { StreamService } from '../../services/stream.service';
styleUrls: ['./interaction-container.component.scss'], styleUrls: ['./interaction-container.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class InteractionContainerComponent extends BaseViewComponentDirective { export class InteractionContainerComponent extends BaseViewComponentDirective implements OnInit {
public showBody = false; public showBody = false;
private streamRunning = false; private streamRunning = false;
@ -63,11 +63,6 @@ export class InteractionContainerComponent extends BaseViewComponentDirective {
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
this.subscriptions.push( this.subscriptions.push(
interactionService.conferenceStateObservable.pipe(distinctUntilChanged()).subscribe(state => {
if (state) {
this.clearTitles();
}
}),
rtcService.showCallDialogObservable.subscribe(show => { rtcService.showCallDialogObservable.subscribe(show => {
if (show) { if (show) {
this.showBody = false; this.showBody = false;
@ -91,9 +86,20 @@ export class InteractionContainerComponent extends BaseViewComponentDirective {
); );
} }
public ngOnInit(): void {
this.subscriptions.push(
this.interactionService.conferenceStateObservable.pipe(distinctUntilChanged()).subscribe(state => {
if (state) {
this.clearTitles();
}
})
);
}
private clearTitles(): void { private clearTitles(): void {
this.containerHeadTitle = ''; this.containerHeadTitle = '';
this.containerHeadSubtitle = ''; this.containerHeadSubtitle = '';
this.cd.markForCheck();
this.cd.detectChanges(); this.cd.detectChanges();
} }
@ -104,14 +110,14 @@ export class InteractionContainerComponent extends BaseViewComponentDirective {
public updateTitle(title: string): void { public updateTitle(title: string): void {
if (title !== this.containerHeadTitle) { if (title !== this.containerHeadTitle) {
this.containerHeadTitle = title ?? ''; this.containerHeadTitle = title ?? '';
this.cd.detectChanges(); this.cd.markForCheck();
} }
} }
public updateSubtitle(title: string): void { public updateSubtitle(title: string): void {
if (title !== this.containerHeadSubtitle) { if (title !== this.containerHeadSubtitle) {
this.containerHeadSubtitle = title ?? ''; this.containerHeadSubtitle = title ?? '';
this.cd.detectChanges(); this.cd.markForCheck();
} }
} }
} }

View File

@ -88,7 +88,7 @@ export class CallRestrictionService {
) { ) {
this.hasToEnterCallSubject.next(); this.hasToEnterCallSubject.next();
} }
} else if (operatorClosIndex === UserListIndexType.NotOnList && this.restricted) { } else if (operatorClosIndex === UserListIndexType.NotOnList && this.restricted && !this.canManageSpeaker) {
this.hasToLeaveCallSubject.next(); this.hasToLeaveCallSubject.next();
} }
} }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
import { CallRestrictionService } from './call-restriction.service'; import { CallRestrictionService } from './call-restriction.service';
@ -9,9 +9,9 @@ import { RtcService } from './rtc.service';
import { StreamService } from './stream.service'; import { StreamService } from './stream.service';
export enum ConferenceState { export enum ConferenceState {
none, none = 1,
stream, stream = 2,
jitsi jitsi = 3
} }
@Injectable({ @Injectable({
@ -20,17 +20,14 @@ export enum ConferenceState {
export class InteractionService { export class InteractionService {
private conferenceStateSubject = new BehaviorSubject<ConferenceState>(ConferenceState.none); private conferenceStateSubject = new BehaviorSubject<ConferenceState>(ConferenceState.none);
public conferenceStateObservable = this.conferenceStateSubject.asObservable(); public conferenceStateObservable = this.conferenceStateSubject.asObservable();
public showLiveConfObservable: Observable<boolean>; public showLiveConfObservable: Observable<boolean> = this.configService.get<boolean>(
'general_system_conference_show'
);
private get conferenceState(): ConferenceState { private get conferenceState(): ConferenceState {
return this.conferenceStateSubject.value; return this.conferenceStateSubject.value;
} }
private isJitsiEnabled: boolean;
private isInCall: boolean; private isInCall: boolean;
private isJitsiActive: boolean;
private hasLiveStreamUrl: boolean;
private canSeeLiveStream: boolean;
private showLiveConf: boolean;
public get isConfStateStream(): Observable<boolean> { public get isConfStateStream(): Observable<boolean> {
return this.conferenceStateObservable.pipe(map(state => state === ConferenceState.stream)); return this.conferenceStateObservable.pipe(map(state => state === ConferenceState.stream));
@ -50,35 +47,37 @@ export class InteractionService {
private rtcService: RtcService, private rtcService: RtcService,
private callRestrictionService: CallRestrictionService private callRestrictionService: CallRestrictionService
) { ) {
this.showLiveConfObservable = this.configService.get<boolean>('general_system_conference_show'); combineLatest(
this.showLiveConfObservable,
this.streamService.hasLiveStreamUrlObvervable,
this.streamService.canSeeLiveStreamObservable,
this.rtcService.isJitsiEnabledObservable,
this.rtcService.isJoinedObservable,
this.rtcService.isJitsiActiveObservable,
this.callRestrictionService.canEnterCallObservable,
(showConf, hasStreamUrl, canSeeStream, jitsiEnabled, inCall, jitsiActive, canEnterCall) => {
this.isInCall = inCall;
/** /**
* If you want to somehow simplify this using rxjs merge-map magic or something * most importantly, if there is a call, to not change the state here
* be my guest.
*/ */
this.streamService.liveStreamUrlObservable.subscribe(url => { if (inCall || jitsiActive) {
this.hasLiveStreamUrl = !!url?.trim() ?? false; return;
this.detectDeadState(); }
}); if (hasStreamUrl && canSeeStream) {
return ConferenceState.stream;
this.streamService.canSeeLiveStreamObservable.subscribe(canSee => { } else if (showConf && jitsiEnabled && canEnterCall && (!hasStreamUrl || !canSeeStream)) {
this.canSeeLiveStream = canSee; return ConferenceState.jitsi;
this.detectDeadState(); } else {
}); return ConferenceState.none;
}
this.rtcService.isJitsiEnabledObservable.subscribe(enabled => { }
this.isJitsiEnabled = enabled; )
this.detectDeadState(); .pipe(distinctUntilChanged())
}); .subscribe(state => {
if (state) {
this.rtcService.isJoinedObservable.subscribe(joined => { this.setConferenceState(state);
this.isInCall = joined; }
this.detectDeadState();
});
this.rtcService.isJitsiActiveObservable.subscribe(isActive => {
this.isJitsiActive = isActive;
this.detectDeadState();
}); });
this.callRestrictionService.hasToEnterCallObservable.subscribe(() => { this.callRestrictionService.hasToEnterCallObservable.subscribe(() => {
@ -91,13 +90,6 @@ export class InteractionService {
this.callRestrictionService.hasToLeaveCallObservable.subscribe(() => { this.callRestrictionService.hasToLeaveCallObservable.subscribe(() => {
this.viewStream(); this.viewStream();
}); });
this.showLiveConfObservable.subscribe(showConf => {
this.showLiveConf = showConf;
this.detectDeadState();
});
this.detectDeadState();
} }
public async enterCall(): Promise<void> { public async enterCall(): Promise<void> {
@ -117,37 +109,4 @@ export class InteractionService {
this.conferenceStateSubject.next(newState); this.conferenceStateSubject.next(newState);
} }
} }
/**
* this is the "dead" state; you would see the jitsi state; but are not connected
* or the connection is prohibited. If this occurs and a live stream
* becomes available, switch to the stream state
*/
private detectDeadState(): void {
if (
this.isInCall === undefined ||
this.isJitsiActive === undefined ||
this.hasLiveStreamUrl === undefined ||
this.conferenceState === undefined ||
this.canSeeLiveStream === undefined ||
this.isJitsiEnabled === undefined
) {
return;
}
/**
* most importantly, if there is a call, to not change the state!
*/
if (this.isInCall || this.isJitsiActive) {
return;
}
if (this.hasLiveStreamUrl && this.canSeeLiveStream) {
this.viewStream();
} else if (this.showLiveConf && (!this.hasLiveStreamUrl || !this.canSeeLiveStream) && this.isJitsiEnabled) {
this.enterCall();
} else {
this.setConferenceState(ConferenceState.none);
}
}
} }

View File

@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { StorageMap } from '@ngx-pwa/local-storage'; import { StorageMap } from '@ngx-pwa/local-storage';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { distinctUntilChanged } from 'rxjs/operators'; import { distinctUntilChanged, map } from 'rxjs/operators';
import { OperatorService, Permission } from 'app/core/core-services/operator.service'; import { OperatorService, Permission } from 'app/core/core-services/operator.service';
import { ConfigService } from 'app/core/ui-services/config.service'; import { ConfigService } from 'app/core/ui-services/config.service';
@ -13,7 +13,10 @@ const STREAM_RUNNING_STORAGE_KEY = 'streamIsRunning';
providedIn: 'root' providedIn: 'root'
}) })
export class StreamService { export class StreamService {
public liveStreamUrlObservable: Observable<string>; public liveStreamUrlObservable: Observable<string> = this.configService.get<string>('general_system_stream_url');
public hasLiveStreamUrlObvervable: Observable<boolean> = this.liveStreamUrlObservable.pipe(
map(url => !!url?.trim() || false)
);
/** /**
* undefined is controlled behavior, meaning, this property was not * undefined is controlled behavior, meaning, this property was not
@ -28,9 +31,11 @@ export class StreamService {
private canSeeLiveStreamSubject = new Subject<boolean>(); private canSeeLiveStreamSubject = new Subject<boolean>();
public canSeeLiveStreamObservable = this.canSeeLiveStreamSubject.asObservable(); public canSeeLiveStreamObservable = this.canSeeLiveStreamSubject.asObservable();
public constructor(private storageMap: StorageMap, operator: OperatorService, configService: ConfigService) { public constructor(
this.liveStreamUrlObservable = configService.get<string>('general_system_stream_url'); private storageMap: StorageMap,
operator: OperatorService,
private configService: ConfigService
) {
this.streamLoadedOnceObservable = this.storageMap this.streamLoadedOnceObservable = this.storageMap
.watch(STREAM_RUNNING_STORAGE_KEY, { type: 'boolean' }) .watch(STREAM_RUNNING_STORAGE_KEY, { type: 'boolean' })
.pipe(distinctUntilChanged()); .pipe(distinctUntilChanged());