You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Merge branch 'develop' into gsouquet/cache-decrypt
This commit is contained in:
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,3 +1,26 @@
|
|||||||
|
Changes in [10.1.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.1.0) (2021-05-10)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.1.0-rc.1...v10.1.0)
|
||||||
|
|
||||||
|
* No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [10.1.0-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.1.0-rc.1) (2021-05-04)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.0.0...v10.1.0-rc.1)
|
||||||
|
|
||||||
|
* Revert "Raise logging dramatically to chase pending event errors"
|
||||||
|
[\#1681](https://github.com/matrix-org/matrix-js-sdk/pull/1681)
|
||||||
|
* Add test coverage collection script
|
||||||
|
[\#1677](https://github.com/matrix-org/matrix-js-sdk/pull/1677)
|
||||||
|
* Raise logging dramatically to chase pending event errors
|
||||||
|
[\#1678](https://github.com/matrix-org/matrix-js-sdk/pull/1678)
|
||||||
|
* Support MSC3086 asserted identity
|
||||||
|
[\#1674](https://github.com/matrix-org/matrix-js-sdk/pull/1674)
|
||||||
|
* Fix `/search` with no results field work again
|
||||||
|
[\#1670](https://github.com/matrix-org/matrix-js-sdk/pull/1670)
|
||||||
|
* Add room.getMembers method
|
||||||
|
[\#1672](https://github.com/matrix-org/matrix-js-sdk/pull/1672)
|
||||||
|
|
||||||
Changes in [10.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.0.0) (2021-04-26)
|
Changes in [10.0.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v10.0.0) (2021-04-26)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.0.0-rc.1...v10.0.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v10.0.0-rc.1...v10.0.0)
|
||||||
|
|||||||
@@ -31,6 +31,23 @@ function addListeners(call) {
|
|||||||
call.hangup();
|
call.hangup();
|
||||||
disableButtons(false, true, true);
|
disableButtons(false, true, true);
|
||||||
});
|
});
|
||||||
|
call.on("feeds_changed", function(feeds) {
|
||||||
|
const localFeed = feeds.find((feed) => feed.isLocal());
|
||||||
|
const remoteFeed = feeds.find((feed) => !feed.isLocal());
|
||||||
|
|
||||||
|
const remoteElement = document.getElementById("remote");
|
||||||
|
const localElement = document.getElementById("local");
|
||||||
|
|
||||||
|
if (remoteFeed) {
|
||||||
|
remoteElement.srcObject = remoteFeed.stream;
|
||||||
|
remoteElement.play();
|
||||||
|
}
|
||||||
|
if (localFeed) {
|
||||||
|
localElement.muted = true;
|
||||||
|
localElement.srcObject = localFeed.stream;
|
||||||
|
localElement.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
@@ -62,10 +79,7 @@ function syncComplete() {
|
|||||||
);
|
);
|
||||||
console.log("Call => %s", call);
|
console.log("Call => %s", call);
|
||||||
addListeners(call);
|
addListeners(call);
|
||||||
call.placeVideoCall(
|
call.placeVideoCall();
|
||||||
document.getElementById("remote"),
|
|
||||||
document.getElementById("local")
|
|
||||||
);
|
|
||||||
document.getElementById("result").innerHTML = "<p>Placed call.</p>";
|
document.getElementById("result").innerHTML = "<p>Placed call.</p>";
|
||||||
disableButtons(true, true, false);
|
disableButtons(true, true, false);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<title>VoIP Test</title>
|
<title>VoIP Test</title>
|
||||||
<script src="lib/matrix.js"></script>
|
<script src="lib/matrix.js"></script>
|
||||||
<script src="browserTest.js"></script>
|
<script src="browserTest.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
You can place and receive calls with this example. Make sure to edit the
|
You can place and receive calls with this example. Make sure to edit the
|
||||||
constants in <code>browserTest.js</code> first.
|
constants in <code>browserTest.js</code> first.
|
||||||
@@ -12,15 +14,21 @@
|
|||||||
<button id="call">Place Call</button>
|
<button id="call">Place Call</button>
|
||||||
<button id="answer">Answer Call</button>
|
<button id="answer">Answer Call</button>
|
||||||
<button id="hangup">Hangup Call</button>
|
<button id="hangup">Hangup Call</button>
|
||||||
<div id="videoBackground">
|
<div id="videoBackground" class="video-background">
|
||||||
<div id="videoContainer">
|
<video class="video-element" id="local"></video>
|
||||||
<video id="remote"></video>
|
<video class="video-element" id="remote"></video>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="videoBackground">
|
|
||||||
<div id="videoContainer">
|
|
||||||
<video id="local"></video>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.video-background {
|
||||||
|
height: 500px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-element {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "matrix-js-sdk",
|
"name": "matrix-js-sdk",
|
||||||
"version": "10.0.0",
|
"version": "10.1.0",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export * from "./content-repo";
|
|||||||
export * as ContentHelpers from "./content-helpers";
|
export * as ContentHelpers from "./content-helpers";
|
||||||
export {
|
export {
|
||||||
createNewMatrixCall,
|
createNewMatrixCall,
|
||||||
setAudioOutput as setMatrixCallAudioOutput,
|
|
||||||
setAudioInput as setMatrixCallAudioInput,
|
setAudioInput as setMatrixCallAudioInput,
|
||||||
setVideoInput as setMatrixCallVideoInput,
|
setVideoInput as setMatrixCallVideoInput,
|
||||||
} from "./webrtc/call";
|
} from "./webrtc/call";
|
||||||
|
|||||||
@@ -28,7 +28,15 @@ import MatrixEvent from '../models/event';
|
|||||||
import {EventType} from '../@types/event';
|
import {EventType} from '../@types/event';
|
||||||
import { RoomMember } from '../models/room-member';
|
import { RoomMember } from '../models/room-member';
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
import { MCallReplacesEvent, MCallAnswer, MCallOfferNegotiate, CallCapabilities } from './callEventTypes';
|
import {
|
||||||
|
MCallReplacesEvent,
|
||||||
|
MCallAnswer,
|
||||||
|
MCallOfferNegotiate,
|
||||||
|
CallCapabilities,
|
||||||
|
SDPStreamMetadataPurpose,
|
||||||
|
} from './callEventTypes';
|
||||||
|
import { CallFeed } from './callFeed';
|
||||||
|
|
||||||
|
|
||||||
// events: hangup, error(err), replaced(call), state(state, oldState)
|
// events: hangup, error(err), replaced(call), state(state, oldState)
|
||||||
|
|
||||||
@@ -106,6 +114,8 @@ export enum CallEvent {
|
|||||||
RemoteHoldUnhold = 'remote_hold_unhold',
|
RemoteHoldUnhold = 'remote_hold_unhold',
|
||||||
// backwards compat alias for LocalHoldUnhold: remove in a major version bump
|
// backwards compat alias for LocalHoldUnhold: remove in a major version bump
|
||||||
HoldUnhold = 'hold_unhold',
|
HoldUnhold = 'hold_unhold',
|
||||||
|
// Feeds have changed
|
||||||
|
FeedsChanged = 'feeds_changed',
|
||||||
|
|
||||||
AssertedIdentityChanged = 'asserted_identity_changed',
|
AssertedIdentityChanged = 'asserted_identity_changed',
|
||||||
}
|
}
|
||||||
@@ -255,11 +265,8 @@ export class MatrixCall extends EventEmitter {
|
|||||||
private candidateSendTries: number;
|
private candidateSendTries: number;
|
||||||
private sentEndOfCandidates: boolean;
|
private sentEndOfCandidates: boolean;
|
||||||
private peerConn: RTCPeerConnection;
|
private peerConn: RTCPeerConnection;
|
||||||
private localVideoElement: HTMLVideoElement;
|
private feeds: Array<CallFeed>;
|
||||||
private remoteVideoElement: HTMLVideoElement;
|
|
||||||
private remoteAudioElement: HTMLAudioElement;
|
|
||||||
private screenSharingStream: MediaStream;
|
private screenSharingStream: MediaStream;
|
||||||
private remoteStream: MediaStream;
|
|
||||||
private localAVStream: MediaStream;
|
private localAVStream: MediaStream;
|
||||||
private inviteOrAnswerSent: boolean;
|
private inviteOrAnswerSent: boolean;
|
||||||
private waitForLocalAVStream: boolean;
|
private waitForLocalAVStream: boolean;
|
||||||
@@ -336,6 +343,8 @@ export class MatrixCall extends EventEmitter {
|
|||||||
this.unholdingRemote = false;
|
this.unholdingRemote = false;
|
||||||
this.micMuted = false;
|
this.micMuted = false;
|
||||||
this.vidMuted = false;
|
this.vidMuted = false;
|
||||||
|
|
||||||
|
this.feeds = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -352,17 +361,11 @@ export class MatrixCall extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Place a video call to this room.
|
* Place a video call to this room.
|
||||||
* @param {Element} remoteVideoElement a <code><video></code> DOM element
|
|
||||||
* to render video to.
|
|
||||||
* @param {Element} localVideoElement a <code><video></code> DOM element
|
|
||||||
* to render the local camera preview.
|
|
||||||
* @throws If you have not specified a listener for 'error' events.
|
* @throws If you have not specified a listener for 'error' events.
|
||||||
*/
|
*/
|
||||||
async placeVideoCall(remoteVideoElement: HTMLVideoElement, localVideoElement: HTMLVideoElement) {
|
async placeVideoCall() {
|
||||||
logger.debug("placeVideoCall");
|
logger.debug("placeVideoCall");
|
||||||
this.checkForErrorListener();
|
this.checkForErrorListener();
|
||||||
this.localVideoElement = localVideoElement;
|
|
||||||
this.remoteVideoElement = remoteVideoElement;
|
|
||||||
const constraints = getUserMediaContraints(ConstraintsType.Video);
|
const constraints = getUserMediaContraints(ConstraintsType.Video);
|
||||||
this.type = CallType.Video;
|
this.type = CallType.Video;
|
||||||
await this.placeCallWithConstraints(constraints);
|
await this.placeCallWithConstraints(constraints);
|
||||||
@@ -372,22 +375,11 @@ export class MatrixCall extends EventEmitter {
|
|||||||
* Place a screen-sharing call to this room. This includes audio.
|
* Place a screen-sharing call to this room. This includes audio.
|
||||||
* <b>This method is EXPERIMENTAL and subject to change without warning. It
|
* <b>This method is EXPERIMENTAL and subject to change without warning. It
|
||||||
* only works in Google Chrome and Firefox >= 44.</b>
|
* only works in Google Chrome and Firefox >= 44.</b>
|
||||||
* @param {Element} remoteVideoElement a <code><video></code> DOM element
|
|
||||||
* to render video to.
|
|
||||||
* @param {Element} localVideoElement a <code><video></code> DOM element
|
|
||||||
* to render the local camera preview.
|
|
||||||
* @throws If you have not specified a listener for 'error' events.
|
* @throws If you have not specified a listener for 'error' events.
|
||||||
*/
|
*/
|
||||||
async placeScreenSharingCall(
|
async placeScreenSharingCall(selectDesktopCapturerSource?: () => Promise<DesktopCapturerSource>) {
|
||||||
remoteVideoElement: HTMLVideoElement,
|
|
||||||
localVideoElement: HTMLVideoElement,
|
|
||||||
selectDesktopCapturerSource?: () => Promise<DesktopCapturerSource>,
|
|
||||||
) {
|
|
||||||
logger.debug("placeScreenSharingCall");
|
logger.debug("placeScreenSharingCall");
|
||||||
this.checkForErrorListener();
|
this.checkForErrorListener();
|
||||||
this.localVideoElement = localVideoElement;
|
|
||||||
this.remoteVideoElement = remoteVideoElement;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource);
|
const screenshareConstraints = await getScreenshareContraints(selectDesktopCapturerSource);
|
||||||
if (!screenshareConstraints) {
|
if (!screenshareConstraints) {
|
||||||
@@ -433,85 +425,53 @@ export class MatrixCall extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the local <code><video></code> DOM element.
|
* Returns an array of all CallFeeds
|
||||||
* @return {Element} The dom element
|
* @returns {Array<CallFeed>} CallFeeds
|
||||||
*/
|
*/
|
||||||
public getLocalVideoElement(): HTMLVideoElement {
|
public getFeeds(): Array<CallFeed> {
|
||||||
return this.localVideoElement;
|
return this.feeds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the remote <code><video></code> DOM element
|
* Returns an array of all local CallFeeds
|
||||||
* used for playing back video capable streams.
|
* @returns {Array<CallFeed>} local CallFeeds
|
||||||
* @return {Element} The dom element
|
|
||||||
*/
|
*/
|
||||||
public getRemoteVideoElement(): HTMLVideoElement {
|
public getLocalFeeds(): Array<CallFeed> {
|
||||||
return this.remoteVideoElement;
|
return this.feeds.filter((feed) => {return feed.isLocal()});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the remote <code><audio></code> DOM element
|
* Returns an array of all remote CallFeeds
|
||||||
* used for playing back audio only streams.
|
* @returns {Array<CallFeed>} remote CallFeeds
|
||||||
* @return {Element} The dom element
|
|
||||||
*/
|
*/
|
||||||
public getRemoteAudioElement(): HTMLAudioElement {
|
public getRemoteFeeds(): Array<CallFeed> {
|
||||||
return this.remoteAudioElement;
|
return this.feeds.filter((feed) => {return !feed.isLocal()});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the local <code><video></code> DOM element. If this call is active,
|
* Returns true if there are no incoming feeds,
|
||||||
* video will be rendered to it immediately.
|
* otherwise returns false
|
||||||
* @param {Element} element The <code><video></code> DOM element.
|
* @returns {boolean} no incoming feeds
|
||||||
*/
|
*/
|
||||||
public async setLocalVideoElement(element: HTMLVideoElement) {
|
public noIncomingFeeds(): boolean {
|
||||||
this.localVideoElement = element;
|
return !this.feeds.some((feed) => !feed.isLocal());
|
||||||
|
|
||||||
if (element && this.localAVStream && this.type === CallType.Video) {
|
|
||||||
element.autoplay = true;
|
|
||||||
|
|
||||||
element.srcObject = this.localAVStream;
|
|
||||||
element.muted = true;
|
|
||||||
try {
|
|
||||||
await element.play();
|
|
||||||
} catch (e) {
|
|
||||||
logger.info("Failed to play local video element", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private pushNewFeed(stream: MediaStream, userId: string, purpose: SDPStreamMetadataPurpose) {
|
||||||
* Set the remote <code><video></code> DOM element. If this call is active,
|
// Try to find a feed with the same stream id as the new stream,
|
||||||
* the first received video-capable stream will be rendered to it immediately.
|
// if we find it replace the old stream with the new one
|
||||||
* @param {Element} element The <code><video></code> DOM element.
|
const feed = this.feeds.find((feed) => feed.stream.id === stream.id);
|
||||||
*/
|
if (feed) {
|
||||||
public setRemoteVideoElement(element: HTMLVideoElement) {
|
feed.setNewStream(stream);
|
||||||
if (element === this.remoteVideoElement) return;
|
} else {
|
||||||
|
this.feeds.push(new CallFeed(stream, userId, purpose, this.client, this.roomId));
|
||||||
element.autoplay = true;
|
this.emit(CallEvent.FeedsChanged, this.feeds);
|
||||||
|
|
||||||
// if we already have an audio element set, use that instead and mute the audio
|
|
||||||
// on this video element.
|
|
||||||
if (this.remoteAudioElement) element.muted = true;
|
|
||||||
|
|
||||||
this.remoteVideoElement = element;
|
|
||||||
|
|
||||||
if (this.remoteStream) {
|
|
||||||
this.playRemoteVideo();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private deleteAllFeeds() {
|
||||||
* Set the remote <code><audio></code> DOM element. If this call is active,
|
this.feeds = [];
|
||||||
* the first received audio-only stream will be rendered to it immediately.
|
this.emit(CallEvent.FeedsChanged, this.feeds);
|
||||||
* The audio will *not* be rendered from the remoteVideoElement.
|
|
||||||
* @param {Element} element The <code><video></code> DOM element.
|
|
||||||
*/
|
|
||||||
public async setRemoteAudioElement(element: HTMLAudioElement) {
|
|
||||||
if (element === this.remoteAudioElement) return;
|
|
||||||
|
|
||||||
this.remoteAudioElement = element;
|
|
||||||
|
|
||||||
if (this.remoteStream) this.playRemoteAudio();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The typescript definitions have this type as 'any' :(
|
// The typescript definitions have this type as 'any' :(
|
||||||
@@ -566,16 +526,18 @@ export class MatrixCall extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remoteStream = this.feeds.find((feed) => {return !feed.isLocal()})?.stream;
|
||||||
|
|
||||||
// According to previous comments in this file, firefox at some point did not
|
// According to previous comments in this file, firefox at some point did not
|
||||||
// add streams until media started ariving on them. Testing latest firefox
|
// add streams until media started ariving on them. Testing latest firefox
|
||||||
// (81 at time of writing), this is no longer a problem, so let's do it the correct way.
|
// (81 at time of writing), this is no longer a problem, so let's do it the correct way.
|
||||||
if (!this.remoteStream || this.remoteStream.getTracks().length === 0) {
|
if (!remoteStream || remoteStream.getTracks().length === 0) {
|
||||||
logger.error("No remote stream or no tracks after setting remote description!");
|
logger.error("No remote stream or no tracks after setting remote description!");
|
||||||
this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);
|
this.terminate(CallParty.Local, CallErrorCode.SetRemoteDescription, false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.type = this.remoteStream.getTracks().some(t => t.kind === 'video') ? CallType.Video : CallType.Voice;
|
this.type = remoteStream.getTracks().some(t => t.kind === 'video') ? CallType.Video : CallType.Voice;
|
||||||
|
|
||||||
this.setState(CallState.Ringing);
|
this.setState(CallState.Ringing);
|
||||||
|
|
||||||
@@ -660,9 +622,6 @@ export class MatrixCall extends EventEmitter {
|
|||||||
newCall.gotUserMediaForAnswer(this.localAVStream);
|
newCall.gotUserMediaForAnswer(this.localAVStream);
|
||||||
delete(this.localAVStream);
|
delete(this.localAVStream);
|
||||||
}
|
}
|
||||||
newCall.localVideoElement = this.localVideoElement;
|
|
||||||
newCall.remoteVideoElement = this.remoteVideoElement;
|
|
||||||
newCall.remoteAudioElement = this.remoteAudioElement;
|
|
||||||
this.successor = newCall;
|
this.successor = newCall;
|
||||||
this.emit(CallEvent.Replaced, newCall);
|
this.emit(CallEvent.Replaced, newCall);
|
||||||
this.hangup(CallErrorCode.Replaced, true);
|
this.hangup(CallErrorCode.Replaced, true);
|
||||||
@@ -774,10 +733,6 @@ export class MatrixCall extends EventEmitter {
|
|||||||
}
|
}
|
||||||
this.updateMuteStatus();
|
this.updateMuteStatus();
|
||||||
|
|
||||||
if (!onHold) {
|
|
||||||
this.playRemoteAudio();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold);
|
this.emit(CallEvent.RemoteHoldUnhold, this.remoteOnHold);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,16 +786,6 @@ export class MatrixCall extends EventEmitter {
|
|||||||
|
|
||||||
const vidShouldBeMuted = this.vidMuted || this.remoteOnHold;
|
const vidShouldBeMuted = this.vidMuted || this.remoteOnHold;
|
||||||
setTracksEnabled(this.localAVStream.getVideoTracks(), !vidShouldBeMuted);
|
setTracksEnabled(this.localAVStream.getVideoTracks(), !vidShouldBeMuted);
|
||||||
|
|
||||||
if (this.remoteOnHold) {
|
|
||||||
if (this.remoteAudioElement && this.remoteAudioElement.srcObject === this.remoteStream) {
|
|
||||||
this.remoteAudioElement.muted = true;
|
|
||||||
} else if (this.remoteVideoElement && this.remoteVideoElement.srcObject === this.remoteStream) {
|
|
||||||
this.remoteVideoElement.muted = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.playRemoteAudio();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -863,24 +808,13 @@ export class MatrixCall extends EventEmitter {
|
|||||||
|
|
||||||
logger.debug("gotUserMediaForInvite -> " + this.type);
|
logger.debug("gotUserMediaForInvite -> " + this.type);
|
||||||
|
|
||||||
const videoEl = this.getLocalVideoElement();
|
|
||||||
|
|
||||||
if (videoEl && this.type === CallType.Video) {
|
|
||||||
videoEl.autoplay = true;
|
|
||||||
if (this.screenSharingStream) {
|
if (this.screenSharingStream) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Setting screen sharing stream to the local video element",
|
"Setting screen sharing stream to the local video element",
|
||||||
);
|
);
|
||||||
videoEl.srcObject = this.screenSharingStream;
|
this.pushNewFeed(this.screenSharingStream, this.client.getUserId(), SDPStreamMetadataPurpose.Screenshare);
|
||||||
} else {
|
} else {
|
||||||
videoEl.srcObject = stream;
|
this.pushNewFeed(stream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia);
|
||||||
}
|
|
||||||
videoEl.muted = true;
|
|
||||||
try {
|
|
||||||
await videoEl.play();
|
|
||||||
} catch (e) {
|
|
||||||
logger.info("Failed to play local video element", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// why do we enable audio (and only audio) tracks here? -- matthew
|
// why do we enable audio (and only audio) tracks here? -- matthew
|
||||||
@@ -950,19 +884,7 @@ export class MatrixCall extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localVidEl = this.getLocalVideoElement();
|
this.pushNewFeed(stream, this.client.getUserId(), SDPStreamMetadataPurpose.Usermedia);
|
||||||
|
|
||||||
if (localVidEl && this.type === CallType.Video) {
|
|
||||||
localVidEl.autoplay = true;
|
|
||||||
localVidEl.srcObject = stream;
|
|
||||||
|
|
||||||
localVidEl.muted = true;
|
|
||||||
try {
|
|
||||||
await localVidEl.play();
|
|
||||||
} catch (e) {
|
|
||||||
logger.info("Failed to play local video element", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.localAVStream = stream;
|
this.localAVStream = stream;
|
||||||
logger.info("Got local AV stream with id " + this.localAVStream.id);
|
logger.info("Got local AV stream with id " + this.localAVStream.id);
|
||||||
@@ -1377,32 +1299,31 @@ export class MatrixCall extends EventEmitter {
|
|||||||
logger.warn(`Streamless ${ev.track.kind} found: ignoring.`);
|
logger.warn(`Streamless ${ev.track.kind} found: ignoring.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldRemoteStream = this.feeds.find((feed) => {return !feed.isLocal()})?.stream;
|
||||||
|
|
||||||
// If we already have a stream, check this track is from the same one
|
// If we already have a stream, check this track is from the same one
|
||||||
if (this.remoteStream && ev.streams[0].id !== this.remoteStream.id) {
|
// Note that we check by ID and always set the remote stream: Chrome appears
|
||||||
|
// to make new stream objects when tranciever directionality is changed and the 'active'
|
||||||
|
// status of streams change - Dave
|
||||||
|
if (oldRemoteStream && ev.streams[0].id !== oldRemoteStream.id) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`Ignoring new stream ID ${ev.streams[0].id}: we already have stream ID ${this.remoteStream.id}`,
|
`Ignoring new stream ID ${ev.streams[0].id}: we already have stream ID ${oldRemoteStream.id}`,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.remoteStream) {
|
if (!oldRemoteStream) {
|
||||||
logger.info("Got remote stream with id " + ev.streams[0].id);
|
logger.info("Got remote stream with id " + ev.streams[0].id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that we check by ID above and always set the remote stream: Chrome appears
|
const newRemoteStream = ev.streams[0];
|
||||||
// to make new stream objects when tranciever directionality is changed and the 'active'
|
|
||||||
// status of streams change
|
|
||||||
this.remoteStream = ev.streams[0];
|
|
||||||
|
|
||||||
logger.debug(`Track id ${ev.track.id} of kind ${ev.track.kind} added`);
|
logger.debug(`Track id ${ev.track.id} of kind ${ev.track.kind} added`);
|
||||||
|
|
||||||
if (ev.track.kind === 'video') {
|
this.pushNewFeed(newRemoteStream, this.getOpponentMember().userId, SDPStreamMetadataPurpose.Usermedia)
|
||||||
if (this.remoteVideoElement) {
|
|
||||||
this.playRemoteVideo();
|
logger.info("playing remote. stream active? " + newRemoteStream.active);
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.remoteAudioElement) this.playRemoteAudio();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onNegotiationNeeded = async () => {
|
onNegotiationNeeded = async () => {
|
||||||
@@ -1425,52 +1346,6 @@ export class MatrixCall extends EventEmitter {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async playRemoteAudio() {
|
|
||||||
if (this.remoteVideoElement) this.remoteVideoElement.muted = true;
|
|
||||||
this.remoteAudioElement.muted = false;
|
|
||||||
|
|
||||||
this.remoteAudioElement.srcObject = this.remoteStream;
|
|
||||||
|
|
||||||
// if audioOutput is non-default:
|
|
||||||
try {
|
|
||||||
if (audioOutput) {
|
|
||||||
// This seems quite unreliable in Chrome, although I haven't yet managed to make a jsfiddle where
|
|
||||||
// it fails.
|
|
||||||
// It seems reliable if you set the sink ID after setting the srcObject and then set the sink ID
|
|
||||||
// back to the default after the call is over
|
|
||||||
logger.info("Setting audio sink to " + audioOutput + ", was " + this.remoteAudioElement.sinkId);
|
|
||||||
await this.remoteAudioElement.setSinkId(audioOutput);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn("Couldn't set requested audio output device: using default", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.remoteAudioElement.play();
|
|
||||||
} catch (e) {
|
|
||||||
logger.error("Failed to play remote audio element", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async playRemoteVideo() {
|
|
||||||
// A note on calling methods on media elements:
|
|
||||||
// We used to have queues per media element to serialise all calls on those elements.
|
|
||||||
// The reason given for this was that load() and play() were racing. However, we now
|
|
||||||
// never call load() explicitly so this seems unnecessary. However, serialising every
|
|
||||||
// operation was causing bugs where video would not resume because some play command
|
|
||||||
// had got stuck and all media operations were queued up behind it. If necessary, we
|
|
||||||
// should serialise the ones that need to be serialised but then be able to interrupt
|
|
||||||
// them with another load() which will cancel the pending one, but since we don't call
|
|
||||||
// load() explicitly, it shouldn't be a problem.
|
|
||||||
this.remoteVideoElement.srcObject = this.remoteStream;
|
|
||||||
logger.info("playing remote video. stream active? " + this.remoteStream.active);
|
|
||||||
try {
|
|
||||||
await this.remoteVideoElement.play();
|
|
||||||
} catch (e) {
|
|
||||||
logger.info("Failed to play remote video element", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onHangupReceived = (msg) => {
|
onHangupReceived = (msg) => {
|
||||||
logger.debug("Hangup received for call ID " + this.callId);
|
logger.debug("Hangup received for call ID " + this.callId);
|
||||||
|
|
||||||
@@ -1630,33 +1505,13 @@ export class MatrixCall extends EventEmitter {
|
|||||||
this.inviteTimeout = null;
|
this.inviteTimeout = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const remoteVid = this.getRemoteVideoElement();
|
// Order is important here: first we stopAllMedia() and only then we can deleteAllFeeds()
|
||||||
const remoteAud = this.getRemoteAudioElement();
|
this.stopAllMedia();
|
||||||
const localVid = this.getLocalVideoElement();
|
this.deleteAllFeeds();
|
||||||
|
|
||||||
if (remoteVid) {
|
|
||||||
remoteVid.pause();
|
|
||||||
remoteVid.srcObject = null;
|
|
||||||
}
|
|
||||||
if (remoteAud) {
|
|
||||||
remoteAud.pause();
|
|
||||||
remoteAud.srcObject = null;
|
|
||||||
try {
|
|
||||||
// As per comment in playRemoteAudio, setting the sink ID back to the default
|
|
||||||
// once the call is over makes setSinkId work reliably.
|
|
||||||
await this.remoteAudioElement.setSinkId('')
|
|
||||||
} catch (e) {
|
|
||||||
logger.warn("Failed to set sink ID back to default");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (localVid) {
|
|
||||||
localVid.pause();
|
|
||||||
localVid.srcObject = null;
|
|
||||||
}
|
|
||||||
this.hangupParty = hangupParty;
|
this.hangupParty = hangupParty;
|
||||||
this.hangupReason = hangupReason;
|
this.hangupReason = hangupReason;
|
||||||
this.setState(CallState.Ended);
|
this.setState(CallState.Ended);
|
||||||
this.stopAllMedia();
|
|
||||||
if (this.peerConn && this.peerConn.signalingState !== 'closed') {
|
if (this.peerConn && this.peerConn.signalingState !== 'closed') {
|
||||||
this.peerConn.close();
|
this.peerConn.close();
|
||||||
}
|
}
|
||||||
@@ -1667,19 +1522,9 @@ export class MatrixCall extends EventEmitter {
|
|||||||
|
|
||||||
private stopAllMedia() {
|
private stopAllMedia() {
|
||||||
logger.debug(`stopAllMedia (stream=${this.localAVStream})`);
|
logger.debug(`stopAllMedia (stream=${this.localAVStream})`);
|
||||||
if (this.localAVStream) {
|
|
||||||
for (const track of this.localAVStream.getTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.screenSharingStream) {
|
|
||||||
for (const track of this.screenSharingStream.getTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.remoteStream) {
|
for (const feed of this.feeds) {
|
||||||
for (const track of this.remoteStream.getTracks()) {
|
for (const track of feed.stream.getTracks()) {
|
||||||
track.stop();
|
track.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1910,16 +1755,8 @@ async function getScreenshareContraints(selectDesktopCapturerSource?: () => Prom
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioOutput: string;
|
|
||||||
let audioInput: string;
|
let audioInput: string;
|
||||||
let videoInput: string;
|
let videoInput: string;
|
||||||
/**
|
|
||||||
* Set an audio output device to use for MatrixCalls
|
|
||||||
* @function
|
|
||||||
* @param {string=} deviceId the identifier for the device
|
|
||||||
* undefined treated as unset
|
|
||||||
*/
|
|
||||||
export function setAudioOutput(deviceId: string) { audioOutput = deviceId; }
|
|
||||||
/**
|
/**
|
||||||
* Set an audio input device to use for MatrixCalls
|
* Set an audio input device to use for MatrixCalls
|
||||||
* @function
|
* @function
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
// allow camelcase as these are events type that go onto the wire
|
// allow camelcase as these are events type that go onto the wire
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
|
export enum SDPStreamMetadataPurpose {
|
||||||
|
Usermedia = "m.usermedia",
|
||||||
|
Screenshare = "m.screenshare",
|
||||||
|
}
|
||||||
|
|
||||||
interface CallOfferAnswer {
|
interface CallOfferAnswer {
|
||||||
type: string;
|
type: string;
|
||||||
sdp: string;
|
sdp: string;
|
||||||
|
|||||||
84
src/webrtc/callFeed.ts
Normal file
84
src/webrtc/callFeed.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EventEmitter from "events";
|
||||||
|
import {SDPStreamMetadataPurpose} from "./callEventTypes";
|
||||||
|
import MatrixClient from "../client"
|
||||||
|
import {RoomMember} from "../models/room-member";
|
||||||
|
|
||||||
|
export enum CallFeedEvent {
|
||||||
|
NewStream = "new_stream",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CallFeed extends EventEmitter {
|
||||||
|
constructor(
|
||||||
|
public stream: MediaStream,
|
||||||
|
public userId: string,
|
||||||
|
public purpose: SDPStreamMetadataPurpose,
|
||||||
|
private client: MatrixClient,
|
||||||
|
private roomId: string,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns callRoom member
|
||||||
|
* @returns member of the callRoom
|
||||||
|
*/
|
||||||
|
public getMember(): RoomMember {
|
||||||
|
const callRoom = this.client.getRoom(this.roomId);
|
||||||
|
return callRoom.getMember(this.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if CallFeed is local, otherwise returns false
|
||||||
|
* @returns {boolean} is local?
|
||||||
|
*/
|
||||||
|
public isLocal(): boolean {
|
||||||
|
return this.userId === this.client.getUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: The two following methods should be later replaced
|
||||||
|
// by something that will also check if the remote is muted
|
||||||
|
/**
|
||||||
|
* Returns true if audio is muted or if there are no audio
|
||||||
|
* tracks, otherwise returns false
|
||||||
|
* @returns {boolean} is audio muted?
|
||||||
|
*/
|
||||||
|
public isAudioMuted(): boolean {
|
||||||
|
return this.stream.getAudioTracks().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true video is muted or if there are no video
|
||||||
|
* tracks, otherwise returns false
|
||||||
|
* @returns {boolean} is video muted?
|
||||||
|
*/
|
||||||
|
public isVideoMuted(): boolean {
|
||||||
|
// We assume only one video track
|
||||||
|
return this.stream.getVideoTracks().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current MediaStream with a new one.
|
||||||
|
* This method should be only used by MatrixCall.
|
||||||
|
* @param newStream new stream with which to replace the current one
|
||||||
|
*/
|
||||||
|
public setNewStream(newStream: MediaStream) {
|
||||||
|
this.stream = newStream;
|
||||||
|
this.emit(CallFeedEvent.NewStream, this.stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3522,9 +3522,9 @@ hmac-drbg@^1.0.1:
|
|||||||
minimalistic-crypto-utils "^1.0.1"
|
minimalistic-crypto-utils "^1.0.1"
|
||||||
|
|
||||||
hosted-git-info@^2.1.4:
|
hosted-git-info@^2.1.4:
|
||||||
version "2.8.8"
|
version "2.8.9"
|
||||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
|
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||||
integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
|
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||||
|
|
||||||
html-encoding-sniffer@^2.0.1:
|
html-encoding-sniffer@^2.0.1:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user