livekit-client 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/livekit-client.esm.mjs +123 -38
- package/dist/livekit-client.esm.mjs.map +1 -1
- package/dist/livekit-client.umd.js +1 -1
- package/dist/livekit-client.umd.js.map +1 -1
- package/dist/room/track/RemoteVideoTrack.d.ts +23 -1
- package/dist/room/track/RemoteVideoTrack.d.ts.map +1 -1
- package/dist/room/track/Track.d.ts +2 -1
- package/dist/room/track/Track.d.ts.map +1 -1
- package/dist/room/track/types.d.ts +5 -0
- package/dist/room/track/types.d.ts.map +1 -1
- package/dist/test/MockMediaStreamTrack.d.ts +26 -0
- package/dist/test/MockMediaStreamTrack.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/package.json +2 -1
- package/src/index.ts +2 -1
- package/src/room/Room.ts +1 -1
- package/src/room/track/LocalVideoTrack.ts +1 -1
- package/src/room/track/RemoteVideoTrack.test.ts +149 -0
- package/src/room/track/RemoteVideoTrack.ts +112 -37
- package/src/room/track/Track.ts +18 -2
- package/src/room/track/types.ts +5 -0
- package/src/test/MockMediaStreamTrack.ts +83 -0
- package/src/version.ts +1 -1
@@ -16,15 +16,37 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
16
16
|
setMuted(muted: boolean): void;
|
17
17
|
attach(): HTMLMediaElement;
|
18
18
|
attach(element: HTMLMediaElement): HTMLMediaElement;
|
19
|
+
/**
|
20
|
+
* Observe an ElementInfo for changes when adaptive streaming.
|
21
|
+
* @param elementInfo
|
22
|
+
* @internal
|
23
|
+
*/
|
24
|
+
observeElementInfo(elementInfo: ElementInfo): void;
|
25
|
+
/**
|
26
|
+
* Stop observing an ElementInfo for changes.
|
27
|
+
* @param elementInfo
|
28
|
+
* @internal
|
29
|
+
*/
|
30
|
+
stopObservingElementInfo(elementInfo: ElementInfo): void;
|
19
31
|
detach(): HTMLMediaElement[];
|
20
32
|
detach(element: HTMLMediaElement): HTMLMediaElement;
|
21
33
|
protected monitorReceiver: () => Promise<void>;
|
22
34
|
private getReceiverStats;
|
23
35
|
private stopObservingElement;
|
24
|
-
private handleVisibilityChanged;
|
25
36
|
protected handleAppVisibilityChanged(): Promise<void>;
|
26
37
|
private readonly debouncedHandleResize;
|
27
38
|
private updateVisibility;
|
28
39
|
private updateDimensions;
|
29
40
|
}
|
41
|
+
export interface ElementInfo {
|
42
|
+
element: object;
|
43
|
+
width(): number;
|
44
|
+
height(): number;
|
45
|
+
visible: boolean;
|
46
|
+
visibilityChangedAt: number | undefined;
|
47
|
+
handleResize?: () => void;
|
48
|
+
handleVisibilityChanged?: () => void;
|
49
|
+
observe(): void;
|
50
|
+
stopObserving(): void;
|
51
|
+
}
|
30
52
|
//# sourceMappingURL=RemoteVideoTrack.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"RemoteVideoTrack.d.ts","sourceRoot":"","sources":["../../../src/room/track/RemoteVideoTrack.ts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"RemoteVideoTrack.d.ts","sourceRoot":"","sources":["../../../src/room/track/RemoteVideoTrack.ts"],"names":[],"mappings":"AAIA,OAAO,WAAW,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,sBAAsB,EAAE,MAAM,SAAS,CAAC;AAKjD,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,WAAW;IACvD,gBAAgB;IAChB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAE1B,OAAO,CAAC,SAAS,CAAC,CAAqB;IAEvC,OAAO,CAAC,YAAY,CAAqB;IAEzC,OAAO,CAAC,sBAAsB,CAAC,CAAyB;IAExD,OAAO,CAAC,WAAW,CAAC,CAAU;IAE9B,OAAO,CAAC,cAAc,CAAC,CAAmB;IAE1C,OAAO,CAAC,aAAa,CAAkB;gBAGrC,UAAU,EAAE,gBAAgB,EAC5B,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,cAAc,EACzB,sBAAsB,CAAC,EAAE,sBAAsB;IASjD,IAAI,gBAAgB,IAAI,OAAO,CAE9B;IAED,IAAI,gBAAgB,qBAOnB;IAED,gBAAgB;IAChB,QAAQ,CAAC,KAAK,EAAE,OAAO;IAavB,MAAM,IAAI,gBAAgB;IAC1B,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB;IAqBnD;;;;OAIG;IACH,kBAAkB,CAAC,WAAW,EAAE,WAAW;IAqB3C;;;;OAIG;IACH,wBAAwB,CAAC,WAAW,EAAE,WAAW;IASjD,MAAM,IAAI,gBAAgB,EAAE;IAC5B,MAAM,CAAC,OAAO,EAAE,gBAAgB,GAAG,gBAAgB;IAgBnD,SAAS,CAAC,eAAe,sBAevB;YAEY,gBAAgB;IA8B9B,OAAO,CAAC,oBAAoB;cAQZ,0BAA0B;IAM1C,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAEnB;IAEnB,OAAO,CAAC,gBAAgB;IA4BxB,OAAO,CAAC,gBAAgB;CAyBzB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,IAAI,MAAM,CAAC;IAChB,MAAM,IAAI,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,MAAM,GAAG,SAAS,CAAC;IAExC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,uBAAuB,CAAC,EAAE,MAAM,IAAI,CAAC;IACrC,OAAO,IAAI,IAAI,CAAC;IAChB,aAAa,IAAI,IAAI,CAAC;CACvB"}
|
@@ -17,6 +17,7 @@ export declare class Track extends Track_base {
|
|
17
17
|
mediaStream?: MediaStream;
|
18
18
|
protected _mediaStreamTrack: MediaStreamTrack;
|
19
19
|
protected isInBackground: boolean;
|
20
|
+
private backgroundTimeout;
|
20
21
|
protected _currentBitrate: number;
|
21
22
|
protected constructor(mediaTrack: MediaStreamTrack, kind: Track.Kind);
|
22
23
|
/** current receive bits per second */
|
@@ -43,7 +44,7 @@ export declare class Track extends Track_base {
|
|
43
44
|
protected enable(): void;
|
44
45
|
protected disable(): void;
|
45
46
|
private recycleElement;
|
46
|
-
appVisibilityChangedListener: () => void;
|
47
|
+
protected appVisibilityChangedListener: () => void;
|
47
48
|
protected handleAppVisibilityChanged(): Promise<void>;
|
48
49
|
}
|
49
50
|
/** @internal */
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"Track.d.ts","sourceRoot":"","sources":["../../../src/room/track/Track.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,iBAAiB,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,WAAW,IAAI,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;
|
1
|
+
{"version":3,"file":"Track.d.ts","sourceRoot":"","sources":["../../../src/room/track/Track.ts"],"names":[],"mappings":"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"}
|
@@ -15,5 +15,10 @@ export declare type AdaptiveStreamSettings = {
|
|
15
15
|
* streaming on high definition screens.
|
16
16
|
*/
|
17
17
|
pixelDensity?: number | 'screen';
|
18
|
+
/**
|
19
|
+
* If true, video gets paused when switching to another tab.
|
20
|
+
* Defaults to true.
|
21
|
+
*/
|
22
|
+
pauseVideoInBackground?: boolean;
|
18
23
|
};
|
19
24
|
//# sourceMappingURL=types.d.ts.map
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/room/track/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,oBAAY,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAC9D,oBAAY,UAAU,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAC5D,oBAAY,UAAU,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAE5D,oBAAY,sBAAsB,GAAG;IACnC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAClC,CAAC"}
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/room/track/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,eAAe,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AACvD,OAAO,KAAK,gBAAgB,MAAM,oBAAoB,CAAC;AAEvD,oBAAY,WAAW,GAAG,gBAAgB,GAAG,gBAAgB,CAAC;AAC9D,oBAAY,UAAU,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAC5D,oBAAY,UAAU,GAAG,gBAAgB,GAAG,eAAe,CAAC;AAE5D,oBAAY,sBAAsB,GAAG;IACnC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACjC;;;OAGG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC,CAAC"}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
export default class MockMediaStreamTrack implements MediaStreamTrack {
|
2
|
+
contentHint: string;
|
3
|
+
enabled: boolean;
|
4
|
+
id: string;
|
5
|
+
kind: string;
|
6
|
+
label: string;
|
7
|
+
muted: boolean;
|
8
|
+
onended: ((this: MediaStreamTrack, ev: Event) => any) | null;
|
9
|
+
onmute: ((this: MediaStreamTrack, ev: Event) => any) | null;
|
10
|
+
onunmute: ((this: MediaStreamTrack, ev: Event) => any) | null;
|
11
|
+
readyState: MediaStreamTrackState;
|
12
|
+
isolated: boolean;
|
13
|
+
onisolationchange: ((this: MediaStreamTrack, ev: Event) => any) | null;
|
14
|
+
applyConstraints(constraints?: MediaTrackConstraints): Promise<void>;
|
15
|
+
clone(): MediaStreamTrack;
|
16
|
+
getCapabilities(): MediaTrackCapabilities;
|
17
|
+
getConstraints(): MediaTrackConstraints;
|
18
|
+
getSettings(): MediaTrackSettings;
|
19
|
+
stop(): void;
|
20
|
+
addEventListener<K extends keyof MediaStreamTrackEventMap>(type: K, listener: (this: MediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
|
21
|
+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
|
22
|
+
removeEventListener<K extends keyof MediaStreamTrackEventMap>(type: K, listener: (this: MediaStreamTrack, ev: MediaStreamTrackEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
|
23
|
+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
|
24
|
+
dispatchEvent(event: Event): boolean;
|
25
|
+
}
|
26
|
+
//# sourceMappingURL=MockMediaStreamTrack.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"MockMediaStreamTrack.d.ts","sourceRoot":"","sources":["../../src/test/MockMediaStreamTrack.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,gBAAgB;IACnE,WAAW,EAAE,MAAM,CAAM;IAEzB,OAAO,EAAE,OAAO,CAAQ;IAExB,EAAE,EAAE,MAAM,CAAQ;IAElB,IAAI,EAAE,MAAM,CAAW;IAEvB,KAAK,EAAE,MAAM,CAAW;IAExB,KAAK,EAAE,OAAO,CAAS;IAEvB,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAQ;IAEpE,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAQ;IAEnE,QAAQ,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAQ;IAErE,UAAU,EAAE,qBAAqB,CAAU;IAE3C,QAAQ,EAAE,OAAO,CAAS;IAE1B,iBAAiB,EAAE,CAAC,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,KAAK,KAAK,GAAG,CAAC,GAAG,IAAI,CAAQ;IAG9E,gBAAgB,CAAC,WAAW,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE,KAAK,IAAI,gBAAgB;IAIzB,eAAe,IAAI,sBAAsB;IAIzC,cAAc,IAAI,qBAAqB;IAIvC,WAAW,IAAI,kBAAkB;IAIjC,IAAI,IAAI,IAAI;IAIZ,gBAAgB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EACvD,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,GAAG,EAC1E,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI;IACP,gBAAgB,CACd,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,uBAAuB,GAC1C,IAAI;IAKP,mBAAmB,CAAC,CAAC,SAAS,MAAM,wBAAwB,EAC1D,IAAI,EAAE,CAAC,EACP,QAAQ,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,wBAAwB,CAAC,CAAC,CAAC,KAAK,GAAG,EAC1E,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI;IACP,mBAAmB,CACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,kCAAkC,EAC5C,OAAO,CAAC,EAAE,OAAO,GAAG,oBAAoB,GACvC,IAAI;IAKP,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO;CAGrC"}
|
package/dist/version.d.ts
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "livekit-client",
|
3
|
-
"version": "1.0.
|
3
|
+
"version": "1.0.1",
|
4
4
|
"description": "JavaScript/TypeScript client SDK for LiveKit",
|
5
5
|
"main": "./dist/livekit-client.umd.js",
|
6
6
|
"unpkg": "./dist/livekit-client.umd.js",
|
@@ -57,6 +57,7 @@
|
|
57
57
|
"jest": "^27.4.3",
|
58
58
|
"prettier": "^2.6.1",
|
59
59
|
"rollup": "^2.70.1",
|
60
|
+
"rollup-plugin-livereload": "^2.0.5",
|
60
61
|
"rollup-plugin-re": "^1.0.7",
|
61
62
|
"rollup-plugin-serve": "^1.1.0",
|
62
63
|
"rollup-plugin-terser": "^7.0.2",
|
package/src/index.ts
CHANGED
@@ -12,7 +12,7 @@ import LocalVideoTrack from './room/track/LocalVideoTrack';
|
|
12
12
|
import RemoteAudioTrack from './room/track/RemoteAudioTrack';
|
13
13
|
import RemoteTrack from './room/track/RemoteTrack';
|
14
14
|
import RemoteTrackPublication from './room/track/RemoteTrackPublication';
|
15
|
-
import RemoteVideoTrack from './room/track/RemoteVideoTrack';
|
15
|
+
import RemoteVideoTrack, { ElementInfo } from './room/track/RemoteVideoTrack';
|
16
16
|
import { TrackPublication } from './room/track/TrackPublication';
|
17
17
|
|
18
18
|
export * from './options';
|
@@ -46,4 +46,5 @@ export {
|
|
46
46
|
TrackPublication,
|
47
47
|
VideoQuality,
|
48
48
|
ConnectionQuality,
|
49
|
+
ElementInfo,
|
49
50
|
};
|
package/src/room/Room.ts
CHANGED
@@ -560,7 +560,7 @@ class Room extends (EventEmitter as new () => TypedEmitter<RoomEventCallbacks>)
|
|
560
560
|
|
561
561
|
this.localParticipant.tracks.forEach((pub) => {
|
562
562
|
if (pub.track) {
|
563
|
-
this.localParticipant.unpublishTrack(pub.track);
|
563
|
+
this.localParticipant.unpublishTrack(pub.track, shouldStopTracks);
|
564
564
|
}
|
565
565
|
if (shouldStopTracks) {
|
566
566
|
pub.track?.detach();
|
@@ -89,7 +89,7 @@ export default class LocalVideoTrack extends LocalTrack {
|
|
89
89
|
bytesSent: v.bytesSent,
|
90
90
|
framesSent: v.framesSent,
|
91
91
|
timestamp: v.timestamp,
|
92
|
-
rid: v.rid ??
|
92
|
+
rid: v.rid ?? v.id,
|
93
93
|
retransmittedPacketsSent: v.retransmittedPacketsSent,
|
94
94
|
qualityLimitationReason: v.qualityLimitationReason,
|
95
95
|
qualityLimitationResolutionChanges: v.qualityLimitationResolutionChanges,
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import { TrackEvent } from '../events';
|
2
|
+
import RemoteVideoTrack, { ElementInfo } from './RemoteVideoTrack';
|
3
|
+
import MockMediaStreamTrack from '../../test/MockMediaStreamTrack';
|
4
|
+
import { Track } from './Track';
|
5
|
+
|
6
|
+
jest.useFakeTimers();
|
7
|
+
|
8
|
+
describe('RemoteVideoTrack', () => {
|
9
|
+
let track: RemoteVideoTrack;
|
10
|
+
|
11
|
+
beforeEach(() => {
|
12
|
+
track = new RemoteVideoTrack(new MockMediaStreamTrack(), 'sid', undefined, {});
|
13
|
+
});
|
14
|
+
describe('element visibility', () => {
|
15
|
+
let events: boolean[] = [];
|
16
|
+
|
17
|
+
beforeEach(() => {
|
18
|
+
track.on(TrackEvent.VisibilityChanged, (visible) => {
|
19
|
+
events.push(visible);
|
20
|
+
});
|
21
|
+
});
|
22
|
+
afterEach(() => {
|
23
|
+
events = [];
|
24
|
+
});
|
25
|
+
|
26
|
+
it('emits a visibility event upon observing visible element', () => {
|
27
|
+
const elementInfo = new MockElementInfo();
|
28
|
+
elementInfo.visible = true;
|
29
|
+
|
30
|
+
track.observeElementInfo(elementInfo);
|
31
|
+
|
32
|
+
expect(events).toHaveLength(1);
|
33
|
+
expect(events[0]).toBeTruthy();
|
34
|
+
});
|
35
|
+
|
36
|
+
it('emits a visibility event upon element becoming visible', () => {
|
37
|
+
const elementInfo = new MockElementInfo();
|
38
|
+
track.observeElementInfo(elementInfo);
|
39
|
+
|
40
|
+
elementInfo.setVisible(true);
|
41
|
+
|
42
|
+
expect(events).toHaveLength(2);
|
43
|
+
expect(events[1]).toBeTruthy();
|
44
|
+
});
|
45
|
+
|
46
|
+
it('emits a visibility event upon removing only visible element', () => {
|
47
|
+
const elementInfo = new MockElementInfo();
|
48
|
+
elementInfo.visible = true;
|
49
|
+
|
50
|
+
track.observeElementInfo(elementInfo);
|
51
|
+
track.stopObservingElementInfo(elementInfo);
|
52
|
+
|
53
|
+
expect(events).toHaveLength(2);
|
54
|
+
expect(events[1]).toBeFalsy();
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
describe('element dimensions', () => {
|
59
|
+
let events: Track.Dimensions[] = [];
|
60
|
+
|
61
|
+
beforeEach(() => {
|
62
|
+
track.on(TrackEvent.VideoDimensionsChanged, (dimensions) => {
|
63
|
+
events.push(dimensions);
|
64
|
+
});
|
65
|
+
});
|
66
|
+
|
67
|
+
afterEach(() => {
|
68
|
+
events = [];
|
69
|
+
});
|
70
|
+
|
71
|
+
it('emits a dimensions event upon observing element', () => {
|
72
|
+
const elementInfo = new MockElementInfo();
|
73
|
+
elementInfo.setDimensions(100, 100);
|
74
|
+
|
75
|
+
track.observeElementInfo(elementInfo);
|
76
|
+
jest.runAllTimers();
|
77
|
+
|
78
|
+
expect(events).toHaveLength(1);
|
79
|
+
expect(events[0].width).toBe(100);
|
80
|
+
expect(events[0].height).toBe(100);
|
81
|
+
});
|
82
|
+
|
83
|
+
it('emits a dimensions event upon element resize', () => {
|
84
|
+
const elementInfo = new MockElementInfo();
|
85
|
+
elementInfo.setDimensions(100, 100);
|
86
|
+
|
87
|
+
track.observeElementInfo(elementInfo);
|
88
|
+
jest.runAllTimers();
|
89
|
+
|
90
|
+
elementInfo.setDimensions(200, 200);
|
91
|
+
jest.runAllTimers();
|
92
|
+
|
93
|
+
expect(events).toHaveLength(2);
|
94
|
+
expect(events[1].width).toBe(200);
|
95
|
+
expect(events[1].height).toBe(200);
|
96
|
+
});
|
97
|
+
});
|
98
|
+
});
|
99
|
+
|
100
|
+
class MockElementInfo implements ElementInfo {
|
101
|
+
element: object = {};
|
102
|
+
|
103
|
+
private _width = 0;
|
104
|
+
|
105
|
+
private _height = 0;
|
106
|
+
|
107
|
+
setDimensions(width: number, height: number) {
|
108
|
+
let shouldEmit = false;
|
109
|
+
if (this._width !== width) {
|
110
|
+
this._width = width;
|
111
|
+
shouldEmit = true;
|
112
|
+
}
|
113
|
+
if (this._height !== height) {
|
114
|
+
this._height = height;
|
115
|
+
shouldEmit = true;
|
116
|
+
}
|
117
|
+
|
118
|
+
if (shouldEmit) {
|
119
|
+
this.handleResize?.();
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
width(): number {
|
124
|
+
return this._width;
|
125
|
+
}
|
126
|
+
|
127
|
+
height(): number {
|
128
|
+
return this._height;
|
129
|
+
}
|
130
|
+
|
131
|
+
visible = false;
|
132
|
+
|
133
|
+
setVisible = (visible: boolean) => {
|
134
|
+
if (this.visible !== visible) {
|
135
|
+
this.visible = visible;
|
136
|
+
this.handleVisibilityChanged?.();
|
137
|
+
}
|
138
|
+
};
|
139
|
+
|
140
|
+
visibilityChangedAt = 0;
|
141
|
+
|
142
|
+
handleResize?: () => void;
|
143
|
+
|
144
|
+
handleVisibilityChanged?: () => void;
|
145
|
+
|
146
|
+
observe(): void {}
|
147
|
+
|
148
|
+
stopObserving(): void {}
|
149
|
+
}
|
@@ -1,12 +1,7 @@
|
|
1
1
|
import { debounce } from 'ts-debounce';
|
2
2
|
import { TrackEvent } from '../events';
|
3
3
|
import { computeBitrate, monitorFrequency, VideoReceiverStats } from '../stats';
|
4
|
-
import {
|
5
|
-
getIntersectionObserver,
|
6
|
-
getResizeObserver,
|
7
|
-
isMobile,
|
8
|
-
ObservableMediaElement,
|
9
|
-
} from '../utils';
|
4
|
+
import { getIntersectionObserver, getResizeObserver, ObservableMediaElement } from '../utils';
|
10
5
|
import RemoteTrack from './RemoteTrack';
|
11
6
|
import { attachToElement, detachTrack, Track } from './Track';
|
12
7
|
import { AdaptiveStreamSettings } from './types';
|
@@ -85,24 +80,51 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
85
80
|
this.adaptiveStreamSettings &&
|
86
81
|
this.elementInfos.find((info) => info.element === element) === undefined
|
87
82
|
) {
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
(element as ObservableMediaElement).handleVisibilityChanged = this.handleVisibilityChanged;
|
95
|
-
|
96
|
-
getIntersectionObserver().observe(element);
|
97
|
-
getResizeObserver().observe(element);
|
83
|
+
const elementInfo = new HTMLElementInfo(element);
|
84
|
+
this.observeElementInfo(elementInfo);
|
85
|
+
}
|
86
|
+
this.hasUsedAttach = true;
|
87
|
+
return element;
|
88
|
+
}
|
98
89
|
|
90
|
+
/**
|
91
|
+
* Observe an ElementInfo for changes when adaptive streaming.
|
92
|
+
* @param elementInfo
|
93
|
+
* @internal
|
94
|
+
*/
|
95
|
+
observeElementInfo(elementInfo: ElementInfo) {
|
96
|
+
if (
|
97
|
+
this.adaptiveStreamSettings &&
|
98
|
+
this.elementInfos.find((info) => info === elementInfo) === undefined
|
99
|
+
) {
|
100
|
+
elementInfo.handleResize = () => {
|
101
|
+
this.debouncedHandleResize();
|
102
|
+
};
|
103
|
+
elementInfo.handleVisibilityChanged = () => {
|
104
|
+
this.updateVisibility();
|
105
|
+
};
|
106
|
+
this.elementInfos.push(elementInfo);
|
107
|
+
elementInfo.observe();
|
99
108
|
// trigger the first resize update cycle
|
100
109
|
// if the tab is backgrounded, the initial resize event does not fire until
|
101
110
|
// the tab comes into focus for the first time.
|
102
111
|
this.debouncedHandleResize();
|
112
|
+
this.updateVisibility();
|
103
113
|
}
|
104
|
-
|
105
|
-
|
114
|
+
}
|
115
|
+
|
116
|
+
/**
|
117
|
+
* Stop observing an ElementInfo for changes.
|
118
|
+
* @param elementInfo
|
119
|
+
* @internal
|
120
|
+
*/
|
121
|
+
stopObservingElementInfo(elementInfo: ElementInfo) {
|
122
|
+
const stopElementInfos = this.elementInfos.filter((info) => info === elementInfo);
|
123
|
+
for (const info of stopElementInfos) {
|
124
|
+
info.stopObserving();
|
125
|
+
}
|
126
|
+
this.elementInfos = this.elementInfos.filter((info) => info !== elementInfo);
|
127
|
+
this.updateVisibility();
|
106
128
|
}
|
107
129
|
|
108
130
|
detach(): HTMLMediaElement[];
|
@@ -170,26 +192,16 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
170
192
|
}
|
171
193
|
|
172
194
|
private stopObservingElement(element: HTMLMediaElement) {
|
173
|
-
|
174
|
-
|
195
|
+
const stopElementInfos = this.elementInfos.filter((info) => info.element === element);
|
196
|
+
for (const info of stopElementInfos) {
|
197
|
+
info.stopObserving();
|
198
|
+
}
|
175
199
|
this.elementInfos = this.elementInfos.filter((info) => info.element !== element);
|
176
200
|
}
|
177
201
|
|
178
|
-
private handleVisibilityChanged = (entry: IntersectionObserverEntry) => {
|
179
|
-
const { target, isIntersecting } = entry;
|
180
|
-
const elementInfo = this.elementInfos.find((info) => info.element === target);
|
181
|
-
if (elementInfo) {
|
182
|
-
elementInfo.visible = isIntersecting;
|
183
|
-
elementInfo.visibilityChangedAt = Date.now();
|
184
|
-
}
|
185
|
-
this.updateVisibility();
|
186
|
-
};
|
187
|
-
|
188
202
|
protected async handleAppVisibilityChanged() {
|
189
203
|
await super.handleAppVisibilityChanged();
|
190
204
|
if (!this.isAdaptiveStream) return;
|
191
|
-
// on desktop don't pause when tab is backgrounded
|
192
|
-
if (!isMobile()) return;
|
193
205
|
this.updateVisibility();
|
194
206
|
}
|
195
207
|
|
@@ -202,7 +214,12 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
202
214
|
(prev, info) => Math.max(prev, info.visibilityChangedAt || 0),
|
203
215
|
0,
|
204
216
|
);
|
205
|
-
|
217
|
+
|
218
|
+
const backgroundPause =
|
219
|
+
this.adaptiveStreamSettings?.pauseVideoInBackground ?? true // default to true
|
220
|
+
? this.isInBackground
|
221
|
+
: false;
|
222
|
+
const isVisible = this.elementInfos.some((info) => info.visible) && !backgroundPause;
|
206
223
|
|
207
224
|
if (this.lastVisible === isVisible) {
|
208
225
|
return;
|
@@ -226,8 +243,8 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
226
243
|
for (const info of this.elementInfos) {
|
227
244
|
const pixelDensity = this.adaptiveStreamSettings?.pixelDensity ?? 1;
|
228
245
|
const pixelDensityValue = pixelDensity === 'screen' ? window.devicePixelRatio : pixelDensity;
|
229
|
-
const currentElementWidth = info.
|
230
|
-
const currentElementHeight = info.
|
246
|
+
const currentElementWidth = info.width() * pixelDensityValue;
|
247
|
+
const currentElementHeight = info.height() * pixelDensityValue;
|
231
248
|
if (currentElementWidth + currentElementHeight > maxWidth + maxHeight) {
|
232
249
|
maxWidth = currentElementWidth;
|
233
250
|
maxHeight = currentElementHeight;
|
@@ -242,12 +259,70 @@ export default class RemoteVideoTrack extends RemoteTrack {
|
|
242
259
|
width: maxWidth,
|
243
260
|
height: maxHeight,
|
244
261
|
};
|
262
|
+
|
245
263
|
this.emit(TrackEvent.VideoDimensionsChanged, this.lastDimensions, this);
|
246
264
|
}
|
247
265
|
}
|
248
266
|
|
249
|
-
interface ElementInfo {
|
267
|
+
export interface ElementInfo {
|
268
|
+
element: object;
|
269
|
+
width(): number;
|
270
|
+
height(): number;
|
271
|
+
visible: boolean;
|
272
|
+
visibilityChangedAt: number | undefined;
|
273
|
+
|
274
|
+
handleResize?: () => void;
|
275
|
+
handleVisibilityChanged?: () => void;
|
276
|
+
observe(): void;
|
277
|
+
stopObserving(): void;
|
278
|
+
}
|
279
|
+
|
280
|
+
class HTMLElementInfo implements ElementInfo {
|
250
281
|
element: HTMLMediaElement;
|
282
|
+
|
251
283
|
visible: boolean;
|
252
|
-
|
284
|
+
|
285
|
+
visibilityChangedAt: number | undefined;
|
286
|
+
|
287
|
+
handleResize?: () => void;
|
288
|
+
|
289
|
+
handleVisibilityChanged?: () => void;
|
290
|
+
|
291
|
+
constructor(element: HTMLMediaElement, visible: boolean = false) {
|
292
|
+
this.element = element;
|
293
|
+
this.visible = visible;
|
294
|
+
this.visibilityChangedAt = 0;
|
295
|
+
}
|
296
|
+
|
297
|
+
width(): number {
|
298
|
+
return this.element.clientWidth;
|
299
|
+
}
|
300
|
+
|
301
|
+
height(): number {
|
302
|
+
return this.element.clientWidth;
|
303
|
+
}
|
304
|
+
|
305
|
+
observe() {
|
306
|
+
(this.element as ObservableMediaElement).handleResize = () => {
|
307
|
+
this.handleResize?.();
|
308
|
+
};
|
309
|
+
(this.element as ObservableMediaElement).handleVisibilityChanged = this.onVisibilityChanged;
|
310
|
+
|
311
|
+
getIntersectionObserver().observe(this.element);
|
312
|
+
getResizeObserver().observe(this.element);
|
313
|
+
}
|
314
|
+
|
315
|
+
private onVisibilityChanged = (entry: IntersectionObserverEntry) => {
|
316
|
+
const { target, isIntersecting } = entry;
|
317
|
+
if (target === this.element) {
|
318
|
+
this.visible = isIntersecting;
|
319
|
+
this.visibilityChangedAt = Date.now();
|
320
|
+
this.handleVisibilityChanged?.();
|
321
|
+
}
|
322
|
+
};
|
323
|
+
|
324
|
+
stopObserving() {
|
325
|
+
getIntersectionObserver()?.unobserve(this.element);
|
326
|
+
getResizeObserver()?.unobserve(this.element);
|
327
|
+
}
|
253
328
|
}
|
package/src/room/track/Track.ts
CHANGED
@@ -5,6 +5,8 @@ import { StreamState as ProtoStreamState } from '../../proto/livekit_rtc';
|
|
5
5
|
import { TrackEvent } from '../events';
|
6
6
|
import { isFireFox, isSafari, isWeb } from '../utils';
|
7
7
|
|
8
|
+
const BACKGROUND_REACTION_DELAY = 5000;
|
9
|
+
|
8
10
|
// keep old audio elements when detached, we would re-use them since on iOS
|
9
11
|
// Safari tracks which audio elements have been "blessed" by the user.
|
10
12
|
const recycledElements: Array<HTMLAudioElement> = [];
|
@@ -32,6 +34,8 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
|
|
32
34
|
|
33
35
|
protected isInBackground: boolean;
|
34
36
|
|
37
|
+
private backgroundTimeout: ReturnType<typeof setTimeout> | undefined;
|
38
|
+
|
35
39
|
protected _currentBitrate: number = 0;
|
36
40
|
|
37
41
|
protected constructor(mediaTrack: MediaStreamTrack, kind: Track.Kind) {
|
@@ -179,8 +183,20 @@ export class Track extends (EventEmitter as new () => TypedEventEmitter<TrackEve
|
|
179
183
|
}
|
180
184
|
}
|
181
185
|
|
182
|
-
appVisibilityChangedListener = () => {
|
183
|
-
this.
|
186
|
+
protected appVisibilityChangedListener = () => {
|
187
|
+
if (this.backgroundTimeout) {
|
188
|
+
clearTimeout(this.backgroundTimeout);
|
189
|
+
}
|
190
|
+
// delay app visibility update if it goes to hidden
|
191
|
+
// update immediately if it comes back to focus
|
192
|
+
if (document.visibilityState === 'hidden') {
|
193
|
+
this.backgroundTimeout = setTimeout(
|
194
|
+
() => this.handleAppVisibilityChanged(),
|
195
|
+
BACKGROUND_REACTION_DELAY,
|
196
|
+
);
|
197
|
+
} else {
|
198
|
+
this.handleAppVisibilityChanged();
|
199
|
+
}
|
184
200
|
};
|
185
201
|
|
186
202
|
protected async handleAppVisibilityChanged() {
|
package/src/room/track/types.ts
CHANGED
@@ -17,4 +17,9 @@ export type AdaptiveStreamSettings = {
|
|
17
17
|
* streaming on high definition screens.
|
18
18
|
*/
|
19
19
|
pixelDensity?: number | 'screen';
|
20
|
+
/**
|
21
|
+
* If true, video gets paused when switching to another tab.
|
22
|
+
* Defaults to true.
|
23
|
+
*/
|
24
|
+
pauseVideoInBackground?: boolean;
|
20
25
|
};
|