mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-26 21:09:21 +00:00
Compare commits
30 Commits
metadata-l
...
johannes/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0410be33cd | ||
|
|
002f9b0cea | ||
|
|
669f4264c7 | ||
|
|
dc6511b99e | ||
|
|
032968ea2d | ||
|
|
6ce79d8a52 | ||
|
|
b68abb7df4 | ||
|
|
1281ed31df | ||
|
|
e7377fb104 | ||
|
|
edb4dfa9ed | ||
|
|
7db13e1fc0 | ||
|
|
df7451c2cf | ||
|
|
9537c7657c | ||
|
|
6c105e971e | ||
|
|
cbbe6a812e | ||
|
|
3ef3ebe897 | ||
|
|
0c284ed75b | ||
|
|
838d2f3c5d | ||
|
|
2a24fbcee1 | ||
|
|
be8a76868b | ||
|
|
d1ea63587b | ||
|
|
3af160bb02 | ||
|
|
308a042076 | ||
|
|
b554ce4a87 | ||
|
|
b86909af52 | ||
|
|
d6635b7818 | ||
|
|
a6fc710d8c | ||
|
|
5dfd1648c5 | ||
|
|
1d5ac67324 | ||
|
|
2faab3280a |
125
.oxlintrc.json
Normal file
125
.oxlintrc.json
Normal file
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": ["jsdoc", "jsx-a11y", "react", "typescript"],
|
||||
"jsPlugins": [
|
||||
"eslint-plugin-matrix-org"
|
||||
// TODO: Re-enable once oxlint supports lint rules that rely on TypeScript type-awareness.
|
||||
// "eslint-plugin-rxjs"
|
||||
],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"options": {
|
||||
"typeAware": true
|
||||
},
|
||||
"env": {
|
||||
"builtin": true
|
||||
},
|
||||
"rules": {
|
||||
"jsdoc/empty-tags": "error",
|
||||
"jsdoc/check-property-names": "error",
|
||||
"jsdoc/require-param-description": "warn",
|
||||
"matrix-org/require-copyright-header": [
|
||||
"error",
|
||||
"/*\nCopyright %%CURRENT_YEAR%% New Vector Ltd.\n\nSPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial\nPlease see LICENSE in the repository root for full details.\n*/\n\n"
|
||||
],
|
||||
"react/display-name": "error",
|
||||
// TODO: Re-enable once oxlint supports lint rules that rely on TypeScript type-awareness.
|
||||
// "rxjs/no-exposed-subjects": "error",
|
||||
// "rxjs/finnish": [
|
||||
// "error",
|
||||
// {
|
||||
// "names": {
|
||||
// "^this$": false
|
||||
// }
|
||||
// }
|
||||
// ],
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "matrix-widget-api/src",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-widget-api/src/",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-widget-api/src/index",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-widget-api/lib",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-widget-api/lib/",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-widget-api/lib/index",
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/src",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/src/",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/src/index",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/lib",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/lib/",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
},
|
||||
{
|
||||
"name": "matrix-js-sdk/lib/index",
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
}
|
||||
],
|
||||
"patterns": [
|
||||
{
|
||||
"group": [
|
||||
"matrix-widget-api/src",
|
||||
"matrix-widget-api/src/",
|
||||
"matrix-widget-api/src/**",
|
||||
"matrix-widget-api/lib",
|
||||
"matrix-widget-api/lib/",
|
||||
"matrix-widget-api/lib/**"
|
||||
],
|
||||
"message": "Please use matrix-widget-api instead"
|
||||
},
|
||||
{
|
||||
"group": [
|
||||
"matrix-js-sdk/src",
|
||||
"matrix-js-sdk/src/",
|
||||
"matrix-js-sdk/src/**"
|
||||
],
|
||||
"message": "Please use matrix-js-sdk instead"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"typescript/no-floating-promises": "error",
|
||||
"typescript/no-misused-promises": "error",
|
||||
"typescript/promise-function-async": "error",
|
||||
"typescript/require-await": "error",
|
||||
"typescript/await-thenable": "error",
|
||||
"typescript/consistent-type-imports": [
|
||||
"error",
|
||||
{
|
||||
"fixStyle": "inline-type-imports"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
targets: {
|
||||
node: "current",
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
"@babel/preset-react",
|
||||
{
|
||||
runtime: "automatic",
|
||||
},
|
||||
],
|
||||
"@babel/preset-typescript",
|
||||
],
|
||||
plugins: ["babel-plugin-transform-vite-meta-env"],
|
||||
};
|
||||
@@ -7,6 +7,9 @@ A few aspects of Element Call's interface can be controlled through a global API
|
||||
- `controls.canEnterPip(): boolean` Determines whether it's possible to enter picture-in-picture mode.
|
||||
- `controls.enablePip(): void` Puts the call interface into picture-in-picture mode. Throws if not in a call.
|
||||
- `controls.disablePip(): void` Takes the call interface out of picture-in-picture mode, restoring it to its natural display mode. Throws if not in a call.
|
||||
- `controls.onPipMediaOrientationUpdate: ((orientation: "landscape"|"portrait") => void) | undefined` Callback called whenever the PiP media orientation changes.
|
||||
The client should track this value to already initiate the pip in the right orientation.
|
||||
It should update the orientation of the current Pip window when called.
|
||||
|
||||
## Audio devices
|
||||
|
||||
|
||||
5
knip.ts
5
knip.ts
@@ -24,11 +24,6 @@ export default {
|
||||
"normalize.css",
|
||||
// Used for its global type declarations
|
||||
"@types/grecaptcha",
|
||||
// Because we use matrix-js-sdk as a Git dependency rather than consuming
|
||||
// the proper release artifacts, and also import directly from src/, we're
|
||||
// forced to re-install some of the types that it depends on even though
|
||||
// these look unused to Knip
|
||||
"@types/content-type",
|
||||
"@types/sdp-transform",
|
||||
// We obviously use this, but if the package has been linked with pnpm link,
|
||||
// then Knip will flag it as a false positive
|
||||
|
||||
22
package.json
22
package.json
@@ -40,10 +40,6 @@
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.16.5",
|
||||
"@babel/preset-env": "^7.29.5",
|
||||
"@babel/preset-react": "^7.22.15",
|
||||
"@babel/preset-typescript": "^7.23.0",
|
||||
"@codecov/vite-plugin": "^1.3.0",
|
||||
"@fontsource/inconsolata": "^5.1.0",
|
||||
"@fontsource/inter": "^5.1.0",
|
||||
@@ -69,7 +65,6 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@types/content-type": "^1.1.5",
|
||||
"@types/grecaptcha": "^3.0.9",
|
||||
"@types/jsdom": "^21.1.7",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
@@ -84,11 +79,10 @@
|
||||
"@use-gesture/react": "^10.2.11",
|
||||
"@vector-im/compound-design-tokens": "^10.0.0",
|
||||
"@vector-im/compound-web": "^9.3.0",
|
||||
"@vitejs/plugin-react": "^4.0.1",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"@vitest/browser-playwright": "^4.1.5",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"@vitest/ui": "4.1.7",
|
||||
"babel-plugin-transform-vite-meta-env": "^1.0.3",
|
||||
"classnames": "^2.3.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"eslint": "^8.14.0",
|
||||
@@ -148,17 +142,5 @@
|
||||
"vitest": "^4.1.5",
|
||||
"vitest-axe": "^1.0.0-pre.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@livekit/components-core>rxjs": "^7.8.1",
|
||||
"@livekit/track-processors>@mediapipe/tasks-vision": "^0.10.18",
|
||||
"minimatch": "^10.2.3",
|
||||
"tar": "^7.5.11",
|
||||
"glob": "^10.5.0",
|
||||
"qs": "^6.14.1",
|
||||
"js-yaml": "^4.1.1",
|
||||
"esbuild": "^0.28.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.33.0"
|
||||
"packageManager": "pnpm@11.6.0+sha512.9a36518224080c6fe5165afdcfe79bfa118c29be703f3f462b1e32efe1e98e47e8750b148e08286250aad4113cc7993ca413c4e2cd447752708c2ee5751bc95f"
|
||||
}
|
||||
|
||||
1700
pnpm-lock.yaml
generated
1700
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,35 @@
|
||||
# dependencies where we use branches and hashes in the package.json. But that also use a pre/post install script.
|
||||
onlyBuiltDependencies:
|
||||
- "matrix-js-sdk"
|
||||
allowBuilds:
|
||||
"@parcel/watcher": true
|
||||
"@sentry/cli": true
|
||||
"@swc/core": true
|
||||
"core-js": true
|
||||
"esbuild": true
|
||||
"matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/8c95727b6278fe7942c20d0b9485f984dd0694b7": true
|
||||
"protobufjs": true
|
||||
overrides:
|
||||
# We need compatible versions of RxJS in our dependencies and LiveKit's dependencies, but
|
||||
# LiveKit has pinned it to a very specific version which is now holding us back from updating.
|
||||
# See livekit/components-js#1101 for a request for a proper solution.
|
||||
"@livekit/components-core>rxjs": "^7.8.1"
|
||||
# Dedupe Mediapipe dependencies.
|
||||
"@livekit/track-processors>@mediapipe/tasks-vision": "^0.10.18"
|
||||
# Security fix: https://security-tracker.debian.org/tracker/CVE-2026-31802
|
||||
"tar": "^7.5.11"
|
||||
# Security fixes:
|
||||
# - https://github.com/advisories/GHSA-7r86-cg39-jmmj
|
||||
# - https://github.com/advisories/GHSA-23c5-xmqv-rm74
|
||||
"minimatch": "^10.2.3"
|
||||
# Security fix: https://github.com/element-hq/element-call/security/dependabot/109
|
||||
"glob": "^10.5.0"
|
||||
# Security fixes:
|
||||
# - https://github.com/element-hq/element-call/security/dependabot/110
|
||||
# - https://github.com/element-hq/element-call/security/dependabot/122
|
||||
"qs": "^6.14.1"
|
||||
# Security fix: https://github.com/element-hq/element-call/security/dependabot/106
|
||||
"js-yaml": "^4.1.1"
|
||||
# Storybook declares support for 0.27.0 only but empirically works fine with 0.28.0.
|
||||
"esbuild": "^0.28.0"
|
||||
# Multiple security fixes: https://github.com/nodejs/undici/releases/tag/v6.24.0
|
||||
"undici": "^6.24.0"
|
||||
# Security fix: https://github.com/advisories/GHSA-rf6f-7fwh-wjgh
|
||||
"flatted": "^3.4.2"
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface Controls {
|
||||
canEnterPip(): boolean;
|
||||
enablePip(): void;
|
||||
disablePip(): void;
|
||||
onPipMediaOrientationUpdate?: (orientation: "landscape" | "portrait") => void;
|
||||
|
||||
setAvailableAudioDevices(devices: OutputDevice[]): void;
|
||||
setAudioDevice(id: string): void;
|
||||
|
||||
@@ -149,7 +149,7 @@ class IndexedDBLogStore {
|
||||
* @return Resolves when the store is ready.
|
||||
*/
|
||||
public async connect(): Promise<void> {
|
||||
const req = this.indexedDB.open("logs");
|
||||
const req = this.indexedDB.open("logs-element-call");
|
||||
return new Promise((resolve, reject) => {
|
||||
req.onsuccess = (): void => {
|
||||
this.db = req.result;
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { test, vi, onTestFinished, it, describe } from "vitest";
|
||||
import { test, vi, onTestFinished, it, describe, expect } from "vitest";
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
@@ -899,6 +899,41 @@ describe.each([
|
||||
},
|
||||
);
|
||||
|
||||
// TODO add media to lk mocks
|
||||
test("onPipMediaOrientationUpdate is called with the spotlight media orientation", () => {
|
||||
// Set the spy before creating the view model so the initial call is captured
|
||||
const onPipMediaOrientationUpdate = vi.fn();
|
||||
window.controls.onPipMediaOrientationUpdate = onPipMediaOrientationUpdate;
|
||||
onTestFinished(() => {
|
||||
window.controls.onPipMediaOrientationUpdate = undefined;
|
||||
});
|
||||
|
||||
withTestScheduler(({ behavior }) => {
|
||||
// Alice starts as a regular participant, then shares her screen, then stops
|
||||
const aliceSharingInputMarbles = "nyn";
|
||||
|
||||
withCallViewModel(
|
||||
{
|
||||
remoteParticipants$: constant([aliceParticipant]),
|
||||
rtcMembers$: constant([localRtcMember, aliceRtcMember]),
|
||||
sharingScreen: new Map([
|
||||
[aliceParticipant, behavior(aliceSharingInputMarbles, yesNo)],
|
||||
]),
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
});
|
||||
|
||||
// Should be called exactly 3 times:
|
||||
// 1. Initially with "portrait" (Alice is in spotlight as a user, default portrait orientation)
|
||||
// 2. With "landscape" when Alice starts screen sharing (screen shares always use landscape)
|
||||
// 3. With "portrait" again when Alice stops screen sharing and returns to user tile
|
||||
expect(onPipMediaOrientationUpdate).toHaveBeenCalledTimes(3);
|
||||
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(1, "portrait");
|
||||
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(2, "landscape");
|
||||
expect(onPipMediaOrientationUpdate).toHaveBeenNthCalledWith(3, "portrait");
|
||||
});
|
||||
|
||||
test("PiP tile in expanded spotlight layout switches speakers without layout shifts", () => {
|
||||
withTestScheduler(({ behavior, schedule, expectObservable }) => {
|
||||
// Switch to spotlight immediately
|
||||
|
||||
@@ -1203,6 +1203,33 @@ export function createCallViewModel$(
|
||||
})),
|
||||
);
|
||||
|
||||
spotlight$
|
||||
.pipe(
|
||||
switchMap((media) => {
|
||||
let layout;
|
||||
const pipMedia = media[0];
|
||||
if (pipMedia === undefined) return of(undefined);
|
||||
switch (pipMedia.type) {
|
||||
case "user":
|
||||
layout = pipMedia.videoOrientation$;
|
||||
break;
|
||||
case "ringing":
|
||||
layout = of("landscape" as const);
|
||||
break;
|
||||
case "screen share":
|
||||
layout = of("landscape" as const);
|
||||
break;
|
||||
}
|
||||
return layout;
|
||||
}),
|
||||
scope.bind(),
|
||||
)
|
||||
.subscribe((orientation) => {
|
||||
if (orientation === undefined) return;
|
||||
logger.info("controls api pip orientation updated:", orientation);
|
||||
window.controls.onPipMediaOrientationUpdate?.(orientation);
|
||||
});
|
||||
|
||||
/**
|
||||
* The media to be used to produce a layout.
|
||||
*/
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface BaseUserMediaViewModel extends BaseMemberMediaViewModel {
|
||||
audioEnabled$: Behavior<boolean>;
|
||||
videoEnabled$: Behavior<boolean>;
|
||||
videoFit$: Behavior<"cover" | "contain">;
|
||||
videoOrientation$: Behavior<"landscape" | "portrait">;
|
||||
toggleCropVideo: () => void;
|
||||
/**
|
||||
* The expected identity of the LiveKit participant. Exposed for debugging.
|
||||
@@ -104,6 +105,7 @@ export function createBaseUserMedia(
|
||||
{ width: number; height: number } | undefined
|
||||
>(undefined);
|
||||
|
||||
const videoSize$ = videoSizeFromParticipant$(participant$);
|
||||
return {
|
||||
...createMemberMedia(scope, {
|
||||
...inputs,
|
||||
@@ -129,11 +131,14 @@ export function createBaseUserMedia(
|
||||
videoEnabled$: scope.behavior(
|
||||
media$.pipe(map((m) => m?.cameraTrack?.isMuted === false)),
|
||||
),
|
||||
videoFit$: videoFit$(
|
||||
scope,
|
||||
videoSizeFromParticipant$(participant$),
|
||||
targetSize$,
|
||||
videoOrientation$: scope.behavior(
|
||||
videoSize$.pipe(
|
||||
map((s) => (s ? s.width / s.height : 1)),
|
||||
map((aspect) => (aspect > 1 ? "landscape" : "portrait")),
|
||||
),
|
||||
"portrait",
|
||||
),
|
||||
videoFit$: videoFit$(scope, videoSize$, targetSize$),
|
||||
toggleCropVideo: () => toggleCropVideo$.next(),
|
||||
rtcBackendIdentity,
|
||||
handRaised$,
|
||||
|
||||
@@ -32,7 +32,9 @@ export function observeRtpStreamStats$(
|
||||
> {
|
||||
return combineLatest([
|
||||
observeTrackReference$(participant, source),
|
||||
interval(1000).pipe(startWith(0)),
|
||||
// The update frequency is high because we use this value to update the PiP orientation and the fit/fill video tile props based on that
|
||||
// We want it to be responsive. For just the debug tools 1s would be sufficient.
|
||||
interval(350).pipe(startWith(0)),
|
||||
]).pipe(
|
||||
switchMap(async ([trackReference]) => {
|
||||
const track = trackReference?.publication?.track;
|
||||
|
||||
Reference in New Issue
Block a user