Migrating screen sharing to using a store
This commit is contained in:
parent
d32df13f1b
commit
dd428bc1e1
2 changed files with 225 additions and 100 deletions
180
front/src/Stores/ScreenSharingStore.ts
Normal file
180
front/src/Stores/ScreenSharingStore.ts
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
import {derived, get, Readable, readable, writable, Writable} from "svelte/store";
|
||||||
|
import {peerStore} from "./PeerStore";
|
||||||
|
import {localUserStore} from "../Connexion/LocalUserStore";
|
||||||
|
import {ITiledMapGroupLayer, ITiledMapObjectLayer, ITiledMapTileLayer} from "../Phaser/Map/ITiledMap";
|
||||||
|
import {userMovingStore} from "./GameStore";
|
||||||
|
import {HtmlUtils} from "../WebRtc/HtmlUtils";
|
||||||
|
import {
|
||||||
|
audioConstraintStore, cameraEnergySavingStore,
|
||||||
|
enableCameraSceneVisibilityStore,
|
||||||
|
gameOverlayVisibilityStore, LocalStreamStoreValue, privacyShutdownStore,
|
||||||
|
requestedCameraState,
|
||||||
|
requestedMicrophoneState, videoConstraintStore
|
||||||
|
} from "./MediaStore";
|
||||||
|
|
||||||
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store that contains the camera state requested by the user (on or off).
|
||||||
|
*/
|
||||||
|
function createRequestedScreenSharingState() {
|
||||||
|
const { subscribe, set, update } = writable(false);
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe,
|
||||||
|
enableScreenSharing: () => set(true),
|
||||||
|
disableScreenSharing: () => set(false),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const requestedScreenSharingState = createRequestedScreenSharingState();
|
||||||
|
|
||||||
|
let currentStream : MediaStream|null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the camera from filming
|
||||||
|
*/
|
||||||
|
function stopScreenSharing(): void {
|
||||||
|
if (currentStream) {
|
||||||
|
for (const track of currentStream.getVideoTracks()) {
|
||||||
|
track.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let previousComputedVideoConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
let previousComputedAudioConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the media constraints we want to apply.
|
||||||
|
*/
|
||||||
|
export const screenSharingConstraintsStore = derived(
|
||||||
|
[
|
||||||
|
requestedScreenSharingState,
|
||||||
|
gameOverlayVisibilityStore,
|
||||||
|
peerStore,
|
||||||
|
], (
|
||||||
|
[
|
||||||
|
$requestedScreenSharingState,
|
||||||
|
$gameOverlayVisibilityStore,
|
||||||
|
$peerStore,
|
||||||
|
], set
|
||||||
|
) => {
|
||||||
|
|
||||||
|
let currentVideoConstraint: boolean|MediaTrackConstraints = true;
|
||||||
|
let currentAudioConstraint: boolean|MediaTrackConstraints = false;
|
||||||
|
|
||||||
|
// Disable screen sharing if the user requested so
|
||||||
|
if (!$requestedScreenSharingState) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable screen sharing when in a Jitsi
|
||||||
|
if (!$gameOverlayVisibilityStore) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable screen sharing if no peers
|
||||||
|
if ($peerStore.size === 0) {
|
||||||
|
currentVideoConstraint = false;
|
||||||
|
currentAudioConstraint = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's make the changes only if the new value is different from the old one.
|
||||||
|
if (previousComputedVideoConstraint != currentVideoConstraint || previousComputedAudioConstraint != currentAudioConstraint) {
|
||||||
|
previousComputedVideoConstraint = currentVideoConstraint;
|
||||||
|
previousComputedAudioConstraint = currentAudioConstraint;
|
||||||
|
// Let's copy the objects.
|
||||||
|
/*if (typeof previousComputedVideoConstraint !== 'boolean') {
|
||||||
|
previousComputedVideoConstraint = {...previousComputedVideoConstraint};
|
||||||
|
}
|
||||||
|
if (typeof previousComputedAudioConstraint !== 'boolean') {
|
||||||
|
previousComputedAudioConstraint = {...previousComputedAudioConstraint};
|
||||||
|
}*/
|
||||||
|
|
||||||
|
set({
|
||||||
|
video: currentVideoConstraint,
|
||||||
|
audio: currentAudioConstraint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
video: false,
|
||||||
|
audio: false
|
||||||
|
} as MediaStreamConstraints);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A store containing the MediaStream object for ScreenSharing (or null if nothing requested, or Error if an error occurred)
|
||||||
|
*/
|
||||||
|
export const screenSharingLocalStreamStore = derived<Readable<MediaStreamConstraints>, LocalStreamStoreValue>(screenSharingConstraintsStore, ($screenSharingConstraintsStore, set) => {
|
||||||
|
const constraints = $screenSharingConstraintsStore;
|
||||||
|
|
||||||
|
if ($screenSharingConstraintsStore.video === false && $screenSharingConstraintsStore.audio === false) {
|
||||||
|
stopScreenSharing();
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: null,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentStreamPromise: Promise<MediaStream>;
|
||||||
|
if (navigator.getDisplayMedia) {
|
||||||
|
currentStreamPromise = navigator.getDisplayMedia({constraints});
|
||||||
|
} else if (navigator.mediaDevices.getDisplayMedia) {
|
||||||
|
currentStreamPromise = navigator.mediaDevices.getDisplayMedia({constraints});
|
||||||
|
} else {
|
||||||
|
stopScreenSharing();
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: new Error('Your browser does not support sharing screen'),
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
|
stopScreenSharing();
|
||||||
|
currentStream = await currentStreamPromise;
|
||||||
|
|
||||||
|
// If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
|
||||||
|
for (const track of currentStream.getTracks()) {
|
||||||
|
track.onended = () => {
|
||||||
|
stopScreenSharing();
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
|
previousComputedVideoConstraint = false;
|
||||||
|
previousComputedAudioConstraint = false;
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: null,
|
||||||
|
constraints: {
|
||||||
|
video: false,
|
||||||
|
audio: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
set({
|
||||||
|
type: 'success',
|
||||||
|
stream: currentStream,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
currentStream = null;
|
||||||
|
console.info("Error. Unable to share screen.", e);
|
||||||
|
set({
|
||||||
|
type: 'error',
|
||||||
|
error: e,
|
||||||
|
constraints
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
|
@ -12,6 +12,7 @@ import {
|
||||||
requestedCameraState,
|
requestedCameraState,
|
||||||
requestedMicrophoneState
|
requestedMicrophoneState
|
||||||
} from "../Stores/MediaStore";
|
} from "../Stores/MediaStore";
|
||||||
|
import {requestedScreenSharingState, screenSharingLocalStreamStore} from "../Stores/ScreenSharingStore";
|
||||||
|
|
||||||
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
declare const navigator:any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
|
||||||
|
@ -112,13 +113,15 @@ export class MediaManager {
|
||||||
this.monitorClose.style.display = "block";
|
this.monitorClose.style.display = "block";
|
||||||
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
this.monitorClose.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.enableScreenSharing();
|
//this.enableScreenSharing();
|
||||||
|
requestedScreenSharingState.enableScreenSharing();
|
||||||
});
|
});
|
||||||
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
this.monitor = HtmlUtils.getElementByIdOrFail<HTMLImageElement>('monitor');
|
||||||
this.monitor.style.display = "none";
|
this.monitor.style.display = "none";
|
||||||
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
this.monitor.addEventListener('click', (e: MouseEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.disableScreenSharing();
|
//this.disableScreenSharing();
|
||||||
|
requestedScreenSharingState.disableScreenSharing();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.pingCameraStatus();
|
this.pingCameraStatus();
|
||||||
|
@ -173,6 +176,41 @@ export class MediaManager {
|
||||||
this.disableMicrophoneStyle();
|
this.disableMicrophoneStyle();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
//let screenSharingStream : MediaStream|null;
|
||||||
|
screenSharingLocalStreamStore.subscribe((result) => {
|
||||||
|
if (result.type === 'error') {
|
||||||
|
console.error(result.error);
|
||||||
|
layoutManager.addInformation('warning', 'Screen sharing denied. Click here and check navigators permissions.', () => {
|
||||||
|
this.showHelpCameraSettingsCallBack();
|
||||||
|
}, this.userInputManager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.stream !== null) {
|
||||||
|
this.enableScreenSharingStyle();
|
||||||
|
mediaManager.localScreenCapture = result.stream;
|
||||||
|
|
||||||
|
// TODO: migrate this out of MediaManager
|
||||||
|
this.triggerStartedScreenSharingCallbacks(result.stream);
|
||||||
|
|
||||||
|
//screenSharingStream = result.stream;
|
||||||
|
|
||||||
|
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
|
||||||
|
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = result.stream;
|
||||||
|
} else {
|
||||||
|
this.disableScreenSharingStyle();
|
||||||
|
this.removeActiveScreenSharingVideo('me');
|
||||||
|
|
||||||
|
// FIXME: we need the old stream that is being stopped!
|
||||||
|
if (this.localScreenCapture) {
|
||||||
|
this.triggerStoppedScreenSharingCallbacks(this.localScreenCapture);
|
||||||
|
this.localScreenCapture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
//screenSharingStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateScene(){
|
public updateScene(){
|
||||||
|
@ -264,109 +302,16 @@ export class MediaManager {
|
||||||
this.microphoneBtn.classList.add("disabled");
|
this.microphoneBtn.classList.add("disabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
private enableScreenSharing() {
|
private enableScreenSharingStyle(){
|
||||||
this.getScreenMedia().then((stream) => {
|
this.monitorClose.style.display = "none";
|
||||||
this.triggerStartedScreenSharingCallbacks(stream);
|
this.monitor.style.display = "block";
|
||||||
this.monitorClose.style.display = "none";
|
this.monitorBtn.classList.add("enabled");
|
||||||
this.monitor.style.display = "block";
|
|
||||||
this.monitorBtn.classList.add("enabled");
|
|
||||||
}, () => {
|
|
||||||
this.monitorClose.style.display = "block";
|
|
||||||
this.monitor.style.display = "none";
|
|
||||||
this.monitorBtn.classList.remove("enabled");
|
|
||||||
|
|
||||||
layoutManager.addInformation('warning', 'Screen sharing access denied. Click here and check navigators permissions.', () => {
|
|
||||||
this.showHelpCameraSettingsCallBack();
|
|
||||||
}, this.userInputManager);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private disableScreenSharing() {
|
private disableScreenSharingStyle(){
|
||||||
this.monitorClose.style.display = "block";
|
this.monitorClose.style.display = "block";
|
||||||
this.monitor.style.display = "none";
|
this.monitor.style.display = "none";
|
||||||
this.monitorBtn.classList.remove("enabled");
|
this.monitorBtn.classList.remove("enabled");
|
||||||
this.removeActiveScreenSharingVideo('me');
|
|
||||||
this.localScreenCapture?.getTracks().forEach((track: MediaStreamTrack) => {
|
|
||||||
track.stop();
|
|
||||||
});
|
|
||||||
if (this.localScreenCapture === null) {
|
|
||||||
console.warn('Weird: trying to remove a screen sharing that is not enabled');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const localScreenCapture = this.localScreenCapture;
|
|
||||||
//this.getCamera().then((stream) => {
|
|
||||||
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
|
|
||||||
/*}).catch((err) => { //catch error get camera
|
|
||||||
console.error(err);
|
|
||||||
this.triggerStoppedScreenSharingCallbacks(localScreenCapture);
|
|
||||||
});*/
|
|
||||||
this.localScreenCapture = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//get screen
|
|
||||||
getScreenMedia() : Promise<MediaStream>{
|
|
||||||
try {
|
|
||||||
return this._startScreenCapture()
|
|
||||||
.then((stream: MediaStream) => {
|
|
||||||
this.localScreenCapture = stream;
|
|
||||||
|
|
||||||
// If stream ends (for instance if user clicks the stop screen sharing button in the browser), let's close the view
|
|
||||||
for (const track of stream.getTracks()) {
|
|
||||||
track.onended = () => {
|
|
||||||
this.disableScreenSharing();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
this.addScreenSharingActiveVideo('me', DivImportance.Normal);
|
|
||||||
HtmlUtils.getElementByIdOrFail<HTMLVideoElement>('screen-sharing-me').srcObject = stream;
|
|
||||||
|
|
||||||
return stream;
|
|
||||||
})
|
|
||||||
.catch((err: unknown) => {
|
|
||||||
console.error("Error => getScreenMedia => ", err);
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}catch (err) {
|
|
||||||
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _startScreenCapture() {
|
|
||||||
if (navigator.getDisplayMedia) {
|
|
||||||
return navigator.getDisplayMedia({video: true});
|
|
||||||
} else if (navigator.mediaDevices.getDisplayMedia) {
|
|
||||||
return navigator.mediaDevices.getDisplayMedia({video: true});
|
|
||||||
} else {
|
|
||||||
return new Promise((resolve, reject) => { // eslint-disable-line no-unused-vars
|
|
||||||
reject("error sharing screen");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the camera from filming
|
|
||||||
*/
|
|
||||||
public stopCamera(): void {
|
|
||||||
if (this.localStream) {
|
|
||||||
for (const track of this.localStream.getVideoTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops the microphone from listening
|
|
||||||
*/
|
|
||||||
public stopMicrophone(): void {
|
|
||||||
if (this.localStream) {
|
|
||||||
for (const track of this.localStream.getAudioTracks()) {
|
|
||||||
track.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//this.mySoundMeter?.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
addActiveVideo(user: UserSimplePeerInterface, userName: string = ""){
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue