(feat): improve mobile
(feat): add camera sharing (fix): vite tauri connection (fix): methanium/ui theme not applied to call popout
This commit is contained in:
parent
b5ce3c554d
commit
62aaa7c01d
31 changed files with 1915 additions and 859 deletions
154
packages/call/src/mediaShare/controller.ts
Normal file
154
packages/call/src/mediaShare/controller.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
import { log } from "@tensamin/shared/log";
|
||||
import { type LocalTrack, Room, Track } from "livekit-client";
|
||||
import {
|
||||
getMediaShareAdapter,
|
||||
type MediaShareKind,
|
||||
type MediaShareRequest,
|
||||
type MediaShareSession,
|
||||
} from ".";
|
||||
|
||||
export type LocalMediaShareSession = {
|
||||
tracks: Array<LocalTrack | MediaStreamTrack>;
|
||||
capture: MediaShareSession;
|
||||
};
|
||||
|
||||
type MediaShareStoreState = {
|
||||
screenShareSession: LocalMediaShareSession | null;
|
||||
cameraSession: LocalMediaShareSession | null;
|
||||
};
|
||||
|
||||
type MediaShareStoreSetState = (
|
||||
updater:
|
||||
| Partial<MediaShareStoreState>
|
||||
| ((state: MediaShareStoreState) => Partial<MediaShareStoreState>),
|
||||
) => void;
|
||||
|
||||
type MediaShareControllerOptions = {
|
||||
room: Room;
|
||||
getState: () => MediaShareStoreState;
|
||||
setState: MediaShareStoreSetState;
|
||||
getLocalParticipantId: () => number | null;
|
||||
startWatching: (participantId: number) => void;
|
||||
stopWatching: (participantId: number) => void;
|
||||
syncParticipantState: () => void;
|
||||
};
|
||||
|
||||
export function createMediaShareController({
|
||||
room,
|
||||
getState,
|
||||
setState,
|
||||
getLocalParticipantId,
|
||||
startWatching,
|
||||
stopWatching,
|
||||
syncParticipantState,
|
||||
}: MediaShareControllerOptions) {
|
||||
function getSession(kind: MediaShareKind) {
|
||||
return kind === "screen"
|
||||
? getState().screenShareSession
|
||||
: getState().cameraSession;
|
||||
}
|
||||
|
||||
function setSession(
|
||||
kind: MediaShareKind,
|
||||
session: LocalMediaShareSession | null,
|
||||
) {
|
||||
setState(
|
||||
kind === "screen"
|
||||
? { screenShareSession: session }
|
||||
: { cameraSession: session },
|
||||
);
|
||||
}
|
||||
|
||||
async function clearPublishedShare(kind: MediaShareKind) {
|
||||
const session = getSession(kind);
|
||||
if (!session) return;
|
||||
|
||||
setSession(kind, null);
|
||||
await Promise.all(
|
||||
session.tracks.map((track) =>
|
||||
room.localParticipant.unpublishTrack(track, true).catch((error) => {
|
||||
log(1, "call", "red", `Failed to unpublish ${kind} track`, error);
|
||||
}),
|
||||
),
|
||||
);
|
||||
await session.capture.stop().catch((error) => {
|
||||
log(1, "call", "red", `Failed to stop ${kind} capture`, error);
|
||||
});
|
||||
|
||||
if (kind === "screen") {
|
||||
const localParticipantId = getLocalParticipantId();
|
||||
if (localParticipantId != null) stopWatching(localParticipantId);
|
||||
}
|
||||
}
|
||||
|
||||
async function publishShare(
|
||||
kind: MediaShareKind,
|
||||
capture: MediaShareSession,
|
||||
) {
|
||||
if (capture.tracks.length === 0) {
|
||||
await capture.stop();
|
||||
throw new Error(`No ${kind} tracks were created.`);
|
||||
}
|
||||
|
||||
const published: MediaStreamTrack[] = [];
|
||||
try {
|
||||
for (const track of capture.tracks) {
|
||||
await room.localParticipant.publishTrack(track, {
|
||||
source:
|
||||
track.kind === Track.Kind.Audio
|
||||
? Track.Source.ScreenShareAudio
|
||||
: kind === "screen"
|
||||
? Track.Source.ScreenShare
|
||||
: Track.Source.Camera,
|
||||
});
|
||||
published.push(track);
|
||||
}
|
||||
} catch (error) {
|
||||
await Promise.all(
|
||||
published.map((track) =>
|
||||
room.localParticipant.unpublishTrack(track, true),
|
||||
),
|
||||
);
|
||||
await capture.stop();
|
||||
throw error;
|
||||
}
|
||||
|
||||
for (const track of capture.tracks) {
|
||||
track.addEventListener(
|
||||
"ended",
|
||||
() => {
|
||||
void stop(kind);
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
}
|
||||
|
||||
setSession(kind, { tracks: capture.tracks, capture });
|
||||
syncParticipantState();
|
||||
|
||||
if (kind === "screen") {
|
||||
const localParticipantId = getLocalParticipantId();
|
||||
if (localParticipantId != null) startWatching(localParticipantId);
|
||||
}
|
||||
}
|
||||
|
||||
async function start(request: MediaShareRequest) {
|
||||
await clearPublishedShare(request.kind);
|
||||
const capture = await getMediaShareAdapter().start(request);
|
||||
await publishShare(request.kind, capture);
|
||||
}
|
||||
|
||||
async function stop(kind: MediaShareKind) {
|
||||
await clearPublishedShare(kind);
|
||||
syncParticipantState();
|
||||
}
|
||||
|
||||
async function clearAll() {
|
||||
await Promise.all([
|
||||
clearPublishedShare("screen"),
|
||||
clearPublishedShare("camera"),
|
||||
]);
|
||||
}
|
||||
|
||||
return { clearAll, start, stop };
|
||||
}
|
||||
Loading…
Reference in a new issue