(feat): improved mobile notifications

(feat): add lint rules
(feat): improve markdown inline code box
This commit is contained in:
Alois 2026-08-05 21:41:55 +02:00
commit 4a841de073
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
35 changed files with 777 additions and 287 deletions

View file

@ -17,22 +17,6 @@ type MediaShareStoreState = {
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,
@ -41,7 +25,19 @@ export function createMediaShareController({
startWatching,
stopWatching,
syncParticipantState,
}: MediaShareControllerOptions) {
}: {
room: Room;
getState: () => MediaShareStoreState;
setState: (
updater:
| Partial<MediaShareStoreState>
| ((state: MediaShareStoreState) => Partial<MediaShareStoreState>),
) => void;
getLocalParticipantId: () => number | null;
startWatching: (participantId: number) => void;
stopWatching: (participantId: number) => void;
syncParticipantState: () => void;
}) {
function getSession(kind: MediaShareKind) {
return kind === "screen"
? getState().screenShareSession

View file

@ -8,32 +8,16 @@ import type {
MediaShareSource,
} from "./types";
type MobileMediaApi = {
startScreenShare: (includeAudio: boolean) => void;
stopScreenShare: () => void;
requestCameraPermission: () => void;
};
declare global {
interface Window {
tensaminMobileMedia?: MobileMediaApi;
tensaminMobileMedia?: {
startScreenShare: (includeAudio: boolean) => void;
stopScreenShare: () => void;
requestCameraPermission: () => void;
};
}
}
type FrameDetail = {
data: string;
mimeType: string;
width: number;
height: number;
};
type AudioDetail = {
data: string;
sampleRate: number;
channelCount: number;
encoding: "pcm16le";
};
function eventDetail<T>(event: Event): T {
return (event as CustomEvent<T>).detail;
}
@ -164,7 +148,12 @@ async function startMobileScreen(
};
const onFrame = (event: Event) => {
const detail = eventDetail<FrameDetail>(event);
const detail = eventDetail<{
data: string;
mimeType: string;
width: number;
height: number;
}>(event);
const image = new Image();
image.onload = () => {
if (canvas.width !== detail.width || canvas.height !== detail.height) {
@ -179,7 +168,14 @@ async function startMobileScreen(
};
const onAudio = (event: Event) => {
const bytes = decodeBase64(eventDetail<AudioDetail>(event).data);
const bytes = decodeBase64(
eventDetail<{
data: string;
sampleRate: number;
channelCount: number;
encoding: "pcm16le";
}>(event).data,
);
const samples = new Int16Array(
bytes.buffer,
bytes.byteOffset,