use error view (model) in call view (model)

This commit is contained in:
Bruno Windels 2023-01-12 14:37:28 +01:00
parent 64d6db556a
commit 4070d422cd
3 changed files with 65 additions and 16 deletions

View File

@ -29,6 +29,10 @@ export class ErrorViewModel extends ViewModel<Options> {
return this.getOption("error")?.message; return this.getOption("error")?.message;
} }
get error(): Error {
return this.getOption("error");
}
close() { close() {
this.getOption("onClose")(); this.getOption("onClose")();
} }

View File

@ -20,6 +20,7 @@ import {getStreamVideoTrack, getStreamAudioTrack} from "../../../matrix/calls/co
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
import {EventObservableValue} from "../../../observable/value/EventObservableValue"; import {EventObservableValue} from "../../../observable/value/EventObservableValue";
import {ObservableValueMap} from "../../../observable/map/ObservableValueMap"; import {ObservableValueMap} from "../../../observable/map/ObservableValueMap";
import { ErrorViewModel } from "../../ErrorViewModel";
import type {Room} from "../../../matrix/room/Room"; import type {Room} from "../../../matrix/room/Room";
import type {GroupCall} from "../../../matrix/calls/group/GroupCall"; import type {GroupCall} from "../../../matrix/calls/group/GroupCall";
import type {Member} from "../../../matrix/calls/group/Member"; import type {Member} from "../../../matrix/calls/group/Member";
@ -28,14 +29,17 @@ import type {BaseObservableList} from "../../../observable/list/BaseObservableLi
import type {BaseObservableValue} from "../../../observable/value/BaseObservableValue"; import type {BaseObservableValue} from "../../../observable/value/BaseObservableValue";
import type {Stream} from "../../../platform/types/MediaDevices"; import type {Stream} from "../../../platform/types/MediaDevices";
import type {MediaRepository} from "../../../matrix/net/MediaRepository"; import type {MediaRepository} from "../../../matrix/net/MediaRepository";
import type { Session } from "../../../matrix/Session";
type Options = BaseOptions & { type Options = BaseOptions & {
call: GroupCall, call: GroupCall,
room: Room, room: Room,
session: Session
}; };
export class CallViewModel extends ViewModel<Options> { export class CallViewModel extends ViewModel<Options> {
public readonly memberViewModels: BaseObservableList<IStreamViewModel>; public readonly memberViewModels: BaseObservableList<IStreamViewModel>;
private _errorViewModel?: ErrorViewModel;
constructor(options: Options) { constructor(options: Options) {
super(options); super(options);
@ -88,8 +92,8 @@ export class CallViewModel extends ViewModel<Options> {
return this.call.id; return this.call.id;
} }
get error(): string | undefined { get errorViewModel(): ErrorViewModel | undefined {
return this.call.error?.message; return this._errorViewModel;
} }
private get call(): GroupCall { private get call(): GroupCall {
@ -97,12 +101,19 @@ export class CallViewModel extends ViewModel<Options> {
} }
private onUpdate() { private onUpdate() {
if (this.call.error) {
this._reportError(this.call.error);
}
} }
async hangup() { async hangup() {
try {
if (this.call.hasJoined) { if (this.call.hasJoined) {
await this.call.leave(); await this.call.leave();
} }
} catch (err) {
this._reportError(err);
}
} }
async toggleCamera() { async toggleCamera() {
@ -125,7 +136,6 @@ export class CallViewModel extends ViewModel<Options> {
// unmute but no track? // unmute but no track?
if (muteSettings.microphone && !getStreamAudioTrack(localMedia.userMedia)) { if (muteSettings.microphone && !getStreamAudioTrack(localMedia.userMedia)) {
const stream = await this.platform.mediaDevices.getMediaTracks(true, !muteSettings.camera); const stream = await this.platform.mediaDevices.getMediaTracks(true, !muteSettings.camera);
console.log("got tracks", Array.from(stream.getTracks()).map((t: MediaStreamTrack) => { return {kind: t.kind, id: t.id};}))
await this.call.setMedia(localMedia.withUserMedia(stream)); await this.call.setMedia(localMedia.withUserMedia(stream));
} else { } else {
await this.call.setMuted(muteSettings.toggleMicrophone()); await this.call.setMuted(muteSettings.toggleMicrophone());
@ -133,6 +143,21 @@ export class CallViewModel extends ViewModel<Options> {
this.emitChange(); this.emitChange();
} }
} }
private _reportError(error: Error) {
if (this._errorViewModel?.error === error) {
return;
}
this.disposeTracked(this._errorViewModel);
this._errorViewModel = new ErrorViewModel(this.childOptions({
error,
onClose: () => {
this._errorViewModel = this.disposeTracked(this._errorViewModel);
this.emitChange("errorViewModel");
}
}));
this.emitChange("errorViewModel");
}
} }
class OwnMemberViewModel extends ViewModel<Options> implements IStreamViewModel { class OwnMemberViewModel extends ViewModel<Options> implements IStreamViewModel {
@ -151,7 +176,7 @@ class OwnMemberViewModel extends ViewModel<Options> implements IStreamViewModel
})); }));
} }
get error(): string | undefined { get errorViewModel(): ErrorViewModel | undefined {
return undefined; return undefined;
} }
@ -207,22 +232,25 @@ class OwnMemberViewModel extends ViewModel<Options> implements IStreamViewModel
type MemberOptions = BaseOptions & { type MemberOptions = BaseOptions & {
member: Member, member: Member,
mediaRepository: MediaRepository mediaRepository: MediaRepository,
session: Session
}; };
export class CallMemberViewModel extends ViewModel<MemberOptions> implements IStreamViewModel { export class CallMemberViewModel extends ViewModel<MemberOptions> implements IStreamViewModel {
private _errorViewModel?: ErrorViewModel;
get stream(): Stream | undefined { get stream(): Stream | undefined {
return this.member.remoteMedia?.userMedia; return this.member.remoteMedia?.userMedia;
} }
get error(): string | undefined {
return this.member.error?.message;
}
private get member(): Member { private get member(): Member {
return this.getOption("member"); return this.getOption("member");
} }
get errorViewModel(): ErrorViewModel | undefined {
return this._errorViewModel;
}
get isCameraMuted(): boolean { get isCameraMuted(): boolean {
return this.member.remoteMuteSettings?.camera ?? true; return this.member.remoteMuteSettings?.camera ?? true;
} }
@ -250,7 +278,23 @@ export class CallMemberViewModel extends ViewModel<MemberOptions> implements ISt
} }
onUpdate() { onUpdate() {
this.mapMemberSyncErrorIfNeeded();
} }
private mapMemberSyncErrorIfNeeded() {
if (this.member.error && (!this._errorViewModel || this._errorViewModel.error !== this.member.error)) {
this.disposeTracked(this._errorViewModel);
this._errorViewModel = this.track(new ErrorViewModel(this.childOptions({
error: this.member.error,
onClose: () => {
this._errorViewModel = this.disposeTracked(this._errorViewModel);
this.emitChange("errorViewModel");
},
})));
this.emitChange("errorViewModel");
}
}
compare(other: OwnMemberViewModel | CallMemberViewModel): number { compare(other: OwnMemberViewModel | CallMemberViewModel): number {
if (other instanceof OwnMemberViewModel) { if (other instanceof OwnMemberViewModel) {
return -other.compare(this); return -other.compare(this);
@ -268,5 +312,5 @@ export interface IStreamViewModel extends AvatarSource, ViewModel {
get stream(): Stream | undefined; get stream(): Stream | undefined;
get isCameraMuted(): boolean; get isCameraMuted(): boolean;
get isMicrophoneMuted(): boolean; get isMicrophoneMuted(): boolean;
get error(): string | undefined; get errorViewModel(): ErrorViewModel | undefined;
} }

View File

@ -20,6 +20,7 @@ import {ListView} from "../../general/ListView";
import {classNames} from "../../general/html"; import {classNames} from "../../general/html";
import {Stream} from "../../../../types/MediaDevices"; import {Stream} from "../../../../types/MediaDevices";
import type {CallViewModel, CallMemberViewModel, IStreamViewModel} from "../../../../../domain/session/room/CallViewModel"; import type {CallViewModel, CallMemberViewModel, IStreamViewModel} from "../../../../../domain/session/room/CallViewModel";
import { ErrorView } from "../../general/ErrorView";
export class CallView extends TemplateView<CallViewModel> { export class CallView extends TemplateView<CallViewModel> {
private resizeObserver?: ResizeObserver; private resizeObserver?: ResizeObserver;
@ -44,8 +45,8 @@ export class CallView extends TemplateView<CallViewModel> {
}, onClick: disableTargetCallback(() => vm.toggleCamera())}), }, onClick: disableTargetCallback(() => vm.toggleCamera())}),
t.button({className: "CallView_hangup", onClick: disableTargetCallback(() => vm.hangup())}), t.button({className: "CallView_hangup", onClick: disableTargetCallback(() => vm.hangup())}),
]), ]),
t.if(vm => !!vm.error, t => { t.if(vm => !!vm.errorViewModel, t => {
return t.div({className: "CallView_error"}, vm => vm.error); return t.div({className: "CallView_error"}, t.view(new ErrorView(vm.errorViewModel!)));
}) })
]); ]);
} }
@ -116,8 +117,8 @@ class StreamView extends TemplateView<IStreamViewModel> {
cameraMuted: vm => vm.isCameraMuted, cameraMuted: vm => vm.isCameraMuted,
} }
}), }),
t.if(vm => !!vm.error, t => { t.if(vm => !!vm.errorViewModel, t => {
return t.div({className: "StreamView_error"}, vm => vm.error); return t.div({className: "StreamView_error"}, t.view(new ErrorView(vm.errorViewModel!)));
}) })
]); ]);
} }