diff --git a/src/domain/ErrorViewModel.ts b/src/domain/ErrorViewModel.ts index 9045af80..51a34565 100644 --- a/src/domain/ErrorViewModel.ts +++ b/src/domain/ErrorViewModel.ts @@ -29,6 +29,10 @@ export class ErrorViewModel extends ViewModel { return this.getOption("error")?.message; } + get error(): Error { + return this.getOption("error"); + } + close() { this.getOption("onClose")(); } diff --git a/src/domain/session/room/CallViewModel.ts b/src/domain/session/room/CallViewModel.ts index 1080015f..e87af12a 100644 --- a/src/domain/session/room/CallViewModel.ts +++ b/src/domain/session/room/CallViewModel.ts @@ -20,6 +20,7 @@ import {getStreamVideoTrack, getStreamAudioTrack} from "../../../matrix/calls/co import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar"; import {EventObservableValue} from "../../../observable/value/EventObservableValue"; import {ObservableValueMap} from "../../../observable/map/ObservableValueMap"; +import { ErrorViewModel } from "../../ErrorViewModel"; import type {Room} from "../../../matrix/room/Room"; import type {GroupCall} from "../../../matrix/calls/group/GroupCall"; 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 {Stream} from "../../../platform/types/MediaDevices"; import type {MediaRepository} from "../../../matrix/net/MediaRepository"; +import type { Session } from "../../../matrix/Session"; type Options = BaseOptions & { call: GroupCall, room: Room, + session: Session }; export class CallViewModel extends ViewModel { public readonly memberViewModels: BaseObservableList; + private _errorViewModel?: ErrorViewModel; constructor(options: Options) { super(options); @@ -88,8 +92,8 @@ export class CallViewModel extends ViewModel { return this.call.id; } - get error(): string | undefined { - return this.call.error?.message; + get errorViewModel(): ErrorViewModel | undefined { + return this._errorViewModel; } private get call(): GroupCall { @@ -97,11 +101,18 @@ export class CallViewModel extends ViewModel { } private onUpdate() { + if (this.call.error) { + this._reportError(this.call.error); + } } async hangup() { - if (this.call.hasJoined) { - await this.call.leave(); + try { + if (this.call.hasJoined) { + await this.call.leave(); + } + } catch (err) { + this._reportError(err); } } @@ -125,7 +136,6 @@ export class CallViewModel extends ViewModel { // unmute but no track? if (muteSettings.microphone && !getStreamAudioTrack(localMedia.userMedia)) { 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)); } else { await this.call.setMuted(muteSettings.toggleMicrophone()); @@ -133,6 +143,21 @@ export class CallViewModel extends ViewModel { 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 implements IStreamViewModel { @@ -151,7 +176,7 @@ class OwnMemberViewModel extends ViewModel implements IStreamViewModel })); } - get error(): string | undefined { + get errorViewModel(): ErrorViewModel | undefined { return undefined; } @@ -207,22 +232,25 @@ class OwnMemberViewModel extends ViewModel implements IStreamViewModel type MemberOptions = BaseOptions & { member: Member, - mediaRepository: MediaRepository + mediaRepository: MediaRepository, + session: Session }; export class CallMemberViewModel extends ViewModel implements IStreamViewModel { + private _errorViewModel?: ErrorViewModel; + get stream(): Stream | undefined { return this.member.remoteMedia?.userMedia; } - get error(): string | undefined { - return this.member.error?.message; - } - private get member(): Member { return this.getOption("member"); } + get errorViewModel(): ErrorViewModel | undefined { + return this._errorViewModel; + } + get isCameraMuted(): boolean { return this.member.remoteMuteSettings?.camera ?? true; } @@ -250,7 +278,23 @@ export class CallMemberViewModel extends ViewModel implements ISt } 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 { if (other instanceof OwnMemberViewModel) { return -other.compare(this); @@ -268,5 +312,5 @@ export interface IStreamViewModel extends AvatarSource, ViewModel { get stream(): Stream | undefined; get isCameraMuted(): boolean; get isMicrophoneMuted(): boolean; - get error(): string | undefined; + get errorViewModel(): ErrorViewModel | undefined; } diff --git a/src/platform/web/ui/session/room/CallView.ts b/src/platform/web/ui/session/room/CallView.ts index 87cac99f..eacb3144 100644 --- a/src/platform/web/ui/session/room/CallView.ts +++ b/src/platform/web/ui/session/room/CallView.ts @@ -20,6 +20,7 @@ import {ListView} from "../../general/ListView"; import {classNames} from "../../general/html"; import {Stream} from "../../../../types/MediaDevices"; import type {CallViewModel, CallMemberViewModel, IStreamViewModel} from "../../../../../domain/session/room/CallViewModel"; +import { ErrorView } from "../../general/ErrorView"; export class CallView extends TemplateView { private resizeObserver?: ResizeObserver; @@ -44,8 +45,8 @@ export class CallView extends TemplateView { }, onClick: disableTargetCallback(() => vm.toggleCamera())}), t.button({className: "CallView_hangup", onClick: disableTargetCallback(() => vm.hangup())}), ]), - t.if(vm => !!vm.error, t => { - return t.div({className: "CallView_error"}, vm => vm.error); + t.if(vm => !!vm.errorViewModel, t => { + return t.div({className: "CallView_error"}, t.view(new ErrorView(vm.errorViewModel!))); }) ]); } @@ -116,8 +117,8 @@ class StreamView extends TemplateView { cameraMuted: vm => vm.isCameraMuted, } }), - t.if(vm => !!vm.error, t => { - return t.div({className: "StreamView_error"}, vm => vm.error); + t.if(vm => !!vm.errorViewModel, t => { + return t.div({className: "StreamView_error"}, t.view(new ErrorView(vm.errorViewModel!))); }) ]); }