Move toju-app into own its folder
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import {
|
||||
Injectable,
|
||||
computed,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { loadVoiceSettingsFromStorage, saveVoiceSettingsToStorage } from '../../voice-session';
|
||||
import { ELECTRON_ENTIRE_SCREEN_SOURCE_NAME } from '../domain/screen-share.config';
|
||||
|
||||
export type ScreenShareSourceKind = 'screen' | 'window';
|
||||
|
||||
export interface ScreenShareSourceOption {
|
||||
id: string;
|
||||
kind: ScreenShareSourceKind;
|
||||
name: string;
|
||||
thumbnail: string;
|
||||
}
|
||||
|
||||
export interface ScreenShareSourceSelection {
|
||||
includeSystemAudio: boolean;
|
||||
source: ScreenShareSourceOption;
|
||||
}
|
||||
|
||||
interface ScreenShareSourcePickerRequest {
|
||||
includeSystemAudio: boolean;
|
||||
sources: readonly ScreenShareSourceOption[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ScreenShareSourcePickerService {
|
||||
readonly request = computed(() => this._request());
|
||||
|
||||
private readonly _request = signal<ScreenShareSourcePickerRequest | null>(null);
|
||||
|
||||
private pendingResolve: ((selection: ScreenShareSourceSelection) => void) | null = null;
|
||||
private pendingReject: ((reason?: unknown) => void) | null = null;
|
||||
|
||||
open(
|
||||
sources: readonly Pick<ScreenShareSourceOption, 'id' | 'name' | 'thumbnail'>[],
|
||||
initialIncludeSystemAudio = loadVoiceSettingsFromStorage().includeSystemAudio
|
||||
): Promise<ScreenShareSourceSelection> {
|
||||
if (sources.length === 0) {
|
||||
throw new Error('No desktop capture sources were available.');
|
||||
}
|
||||
|
||||
this.cancelPendingRequest();
|
||||
|
||||
const normalizedSources = sources.map((source) => {
|
||||
const kind = this.getSourceKind(source);
|
||||
|
||||
return {
|
||||
...source,
|
||||
kind,
|
||||
name: this.getSourceDisplayName(source.name, kind)
|
||||
};
|
||||
});
|
||||
|
||||
this._request.set({
|
||||
includeSystemAudio: initialIncludeSystemAudio,
|
||||
sources: normalizedSources
|
||||
});
|
||||
|
||||
return new Promise<ScreenShareSourceSelection>((resolve, reject) => {
|
||||
this.pendingResolve = resolve;
|
||||
this.pendingReject = reject;
|
||||
});
|
||||
}
|
||||
|
||||
confirm(sourceId: string, includeSystemAudio: boolean): void {
|
||||
const activeRequest = this._request();
|
||||
const source = activeRequest?.sources.find((entry) => entry.id === sourceId);
|
||||
const resolve = this.pendingResolve;
|
||||
|
||||
if (!source || !resolve) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearPendingRequest();
|
||||
saveVoiceSettingsToStorage({ includeSystemAudio });
|
||||
resolve({
|
||||
includeSystemAudio,
|
||||
source
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.cancelPendingRequest();
|
||||
}
|
||||
|
||||
private cancelPendingRequest(): void {
|
||||
const reject = this.pendingReject;
|
||||
|
||||
this.clearPendingRequest();
|
||||
|
||||
if (reject) {
|
||||
reject(this.createAbortError());
|
||||
}
|
||||
}
|
||||
|
||||
private clearPendingRequest(): void {
|
||||
this._request.set(null);
|
||||
this.pendingResolve = null;
|
||||
this.pendingReject = null;
|
||||
}
|
||||
|
||||
private getSourceKind(
|
||||
source: Pick<ScreenShareSourceOption, 'id' | 'name'>
|
||||
): ScreenShareSourceKind {
|
||||
return source.id.startsWith('screen') || source.name === ELECTRON_ENTIRE_SCREEN_SOURCE_NAME
|
||||
? 'screen'
|
||||
: 'window';
|
||||
}
|
||||
|
||||
private getSourceDisplayName(name: string, kind: ScreenShareSourceKind): string {
|
||||
const trimmedName = name.trim();
|
||||
|
||||
if (trimmedName) {
|
||||
return trimmedName;
|
||||
}
|
||||
|
||||
return kind === 'screen' ? 'Entire screen' : 'Window';
|
||||
}
|
||||
|
||||
private createAbortError(): Error {
|
||||
if (typeof DOMException !== 'undefined') {
|
||||
return new DOMException('The user aborted a request.', 'AbortError');
|
||||
}
|
||||
|
||||
const error = new Error('The user aborted a request.');
|
||||
|
||||
error.name = 'AbortError';
|
||||
|
||||
return error;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { RealtimeSessionFacade } from '../../../core/realtime';
|
||||
import { ScreenShareStartOptions } from '../domain/screen-share.config';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ScreenShareFacade {
|
||||
readonly isScreenSharing = inject(RealtimeSessionFacade).isScreenSharing;
|
||||
readonly screenStream = inject(RealtimeSessionFacade).screenStream;
|
||||
readonly isScreenShareRemotePlaybackSuppressed = inject(RealtimeSessionFacade).isScreenShareRemotePlaybackSuppressed;
|
||||
readonly forceDefaultRemotePlaybackOutput = inject(RealtimeSessionFacade).forceDefaultRemotePlaybackOutput;
|
||||
readonly onRemoteStream = inject(RealtimeSessionFacade).onRemoteStream;
|
||||
readonly onPeerDisconnected = inject(RealtimeSessionFacade).onPeerDisconnected;
|
||||
|
||||
private readonly realtime = inject(RealtimeSessionFacade);
|
||||
|
||||
getRemoteScreenShareStream(peerId: string): MediaStream | null {
|
||||
return this.realtime.getRemoteScreenShareStream(peerId);
|
||||
}
|
||||
|
||||
async startScreenShare(options: ScreenShareStartOptions): Promise<MediaStream> {
|
||||
return await this.realtime.startScreenShare(options);
|
||||
}
|
||||
|
||||
stopScreenShare(): void {
|
||||
this.realtime.stopScreenShare();
|
||||
}
|
||||
|
||||
syncRemoteScreenShareRequests(peerIds: string[], enabled: boolean): void {
|
||||
this.realtime.syncRemoteScreenShareRequests(peerIds, enabled);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user