-
Notifications
You must be signed in to change notification settings - Fork 812
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(desktop): support share screen (#894)
* feat(desktop): support share screen * refactor(desktop): add comment in share screen * refactor(i18n): add i18n text in desktop screen share * fix(desktop): share screen loading invalid * refactor(desktop): miss destroy share screen * refactor(desktop): disable some mobx member in share screen * fix(desktop): mobx warning when set ShareScreenStore.isWritable * refactor(desktop): add not share screen permission tips * fix(desktop): improve shareScreenStore isWritable
- Loading branch information
1 parent
7efc324
commit 0654db7
Showing
26 changed files
with
854 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,6 +34,7 @@ module.exports = { | |
"lastmile", | ||
"onleave", | ||
"leavechannel", | ||
"videosource", | ||
|
||
// css / less | ||
"minlength", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import { globalStore } from "../stores/GlobalStore"; | ||
import { AGORA } from "../constants/Process"; | ||
import type AgoraSDK from "agora-electron-sdk"; | ||
|
||
export class RTCShareScreen { | ||
public constructor( | ||
private readonly roomUUID: string, | ||
private readonly roomClient: AgoraSDK, | ||
private readonly enableShareScreenStatus: (enable: boolean) => void, | ||
private readonly existOtherUserStream: () => boolean, | ||
) {} | ||
|
||
public enable(isDisplayScreen: boolean, screenID: number): void { | ||
if (!globalStore.rtcShareScreen) { | ||
return; | ||
} | ||
|
||
if (this.existOtherUserStream()) { | ||
return; | ||
} | ||
|
||
this.enableShareScreenStatus(true); | ||
|
||
const rect = { x: 0, y: 0, width: 0, height: 0 }; | ||
const videoSourceParam = { | ||
width: 0, | ||
height: 0, | ||
bitrate: 500, | ||
frameRate: 5, | ||
captureMouseCursor: false, | ||
windowFocus: false, | ||
excludeWindowList: [], | ||
excludeWindowCount: 0, | ||
}; | ||
|
||
this.roomClient.once("videoSourceJoinedSuccess", () => { | ||
this.roomClient.videoSourceSetVideoProfile(43, false); | ||
|
||
if (isDisplayScreen) { | ||
this.roomClient.videoSourceStartScreenCaptureByScreen( | ||
{ | ||
// @ts-ignore | ||
id: screenID, | ||
}, | ||
rect, | ||
videoSourceParam, | ||
); | ||
} else { | ||
this.roomClient.videoSourceStartScreenCaptureByWindow( | ||
screenID, | ||
rect, | ||
videoSourceParam, | ||
); | ||
} | ||
}); | ||
|
||
this.roomClient.videoSourceInitialize(AGORA.APP_ID); | ||
|
||
this.roomClient.videoSourceSetChannelProfile(1); | ||
|
||
this.roomClient.videoSourceJoin( | ||
globalStore.rtcShareScreen.token, | ||
this.roomUUID, | ||
"", | ||
globalStore.rtcShareScreen.uid, | ||
); | ||
} | ||
|
||
public close(): Promise<void> { | ||
return new Promise<void>(resolve => { | ||
this.roomClient.once("videoSourceLeaveChannel", () => { | ||
this.roomClient.videoSourceRelease(); | ||
resolve(); | ||
}); | ||
|
||
this.roomClient.videoSourceLeave(); | ||
}).finally(() => { | ||
this.enableShareScreenStatus(false); | ||
}); | ||
} | ||
|
||
public async destroy(): Promise<void> { | ||
return await this.close(); | ||
} | ||
|
||
public getScreenInfo(): ScreenInfo { | ||
const displayList = this.roomClient.getScreenDisplaysInfo() as ScreenDisplaysInfo[]; | ||
const windowList = this.roomClient.getScreenWindowsInfo() as ScreenWindowsInfo[]; | ||
|
||
return { | ||
displayList, | ||
windowList, | ||
}; | ||
} | ||
} | ||
|
||
export type ScreenDisplaysInfo = { | ||
readonly displayId: { | ||
readonly id: number; | ||
}; | ||
readonly width: number; | ||
readonly height: number; | ||
readonly image: Uint8Array; | ||
readonly isActive: boolean; | ||
readonly isBuiltin: boolean; | ||
readonly isMain: boolean; | ||
}; | ||
|
||
export type ScreenWindowsInfo = { | ||
readonly windowId: number; | ||
readonly name: string; // e.g: Google (website title) | ||
readonly ownerName: string; // e.g: Google Chrome.app | ||
readonly width: number; | ||
readonly height: number; | ||
readonly originWidth: number; | ||
readonly originHeight: number; | ||
readonly image: Uint8Array; | ||
readonly isActive: boolean; | ||
readonly isBuiltin: boolean; | ||
readonly isMain: boolean; | ||
}; | ||
|
||
export type ScreenInfo = { | ||
displayList: ScreenDisplaysInfo[]; | ||
windowList: ScreenWindowsInfo[]; | ||
}; |
17 changes: 17 additions & 0 deletions
17
desktop/renderer-app/src/assets/image/share-screen-active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions
28
desktop/renderer-app/src/components/ShareScreen/ShareScreen/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import "./style.less"; | ||
|
||
import React, { useEffect, useMemo, useRef } from "react"; | ||
import { observer } from "mobx-react-lite"; | ||
import classNames from "classnames"; | ||
import type { ShareScreenStore } from "../../../stores/ShareScreenStore"; | ||
|
||
interface ShareScreenProps { | ||
shareScreenStore: ShareScreenStore; | ||
} | ||
|
||
export const ShareScreen = observer<ShareScreenProps>(function ShareScreen({ shareScreenStore }) { | ||
const ref = useRef<HTMLDivElement>(null); | ||
|
||
useEffect(() => { | ||
if (ref.current) { | ||
shareScreenStore.updateElement(ref.current); | ||
} | ||
}, [shareScreenStore]); | ||
|
||
const classNameList = useMemo(() => { | ||
return classNames("share-screen", { | ||
active: shareScreenStore.existOtherShareScreen, | ||
}); | ||
}, [shareScreenStore.existOtherShareScreen]); | ||
|
||
return <div className={classNameList} ref={ref} />; | ||
}); |
10 changes: 10 additions & 0 deletions
10
desktop/renderer-app/src/components/ShareScreen/ShareScreen/style.less
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
.share-screen { | ||
position: absolute; | ||
z-index: 5; | ||
background-color: black; | ||
|
||
&.active { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
desktop/renderer-app/src/components/ShareScreen/ShareScreenPicker/ScreenList/Utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ScreenInfo } from "../../../../apiMiddleware/share-screen"; | ||
|
||
export const uint8ArrayToImageURL = (buffer: Uint8Array): string => { | ||
return URL.createObjectURL( | ||
new Blob([buffer.buffer], { | ||
type: "image/png", | ||
}), | ||
); | ||
}; | ||
|
||
export const getScreenInfo = ( | ||
info: ScreenInfo["windowList"][0] | ScreenInfo["displayList"][0], | ||
): { | ||
id: number; | ||
isDisplay: boolean; | ||
name: string; | ||
} => { | ||
if ("displayId" in info) { | ||
return { | ||
id: info.displayId.id, | ||
isDisplay: true, | ||
name: "Desktop", | ||
}; | ||
} else { | ||
return { | ||
id: info.windowId, | ||
isDisplay: false, | ||
name: `${info.ownerName} - ${info.name}`, | ||
}; | ||
} | ||
}; |
100 changes: 100 additions & 0 deletions
100
desktop/renderer-app/src/components/ShareScreen/ShareScreenPicker/ScreenList/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
import "./style.less"; | ||
import React, { useCallback, useEffect, useState } from "react"; | ||
import { observer } from "mobx-react-lite"; | ||
import { ScreenInfo } from "../../../../apiMiddleware/share-screen"; | ||
import { getScreenInfo, uint8ArrayToImageURL } from "./Utils"; | ||
import classNames from "classnames"; | ||
import { ShareScreenStore } from "../../../../stores/ShareScreenStore"; | ||
import { message } from "antd"; | ||
import { useTranslation } from "react-i18next"; | ||
|
||
interface ScreenListProps { | ||
screenInfo: ScreenInfo; | ||
shareScreenStore: ShareScreenStore; | ||
} | ||
|
||
export const ScreenList = observer<ScreenListProps>(function ShareScreen({ | ||
screenInfo, | ||
shareScreenStore, | ||
}) { | ||
const [activeInfo, setActiveInfo] = useState(""); | ||
const { t } = useTranslation(); | ||
|
||
useEffect(() => { | ||
if (screenInfo.windowList.length === 0) { | ||
void message.error(t("share-screen.desktop-not-permission")); | ||
} | ||
}, [screenInfo.windowList, t]); | ||
|
||
const onClick = useCallback( | ||
(isDisplay: boolean, id: number) => { | ||
setActiveInfo(`${isDisplay ? "display" : "window"}-${id}`); | ||
shareScreenStore.updateIsDisplayScreen(isDisplay); | ||
shareScreenStore.updateScreenID(id); | ||
}, | ||
[shareScreenStore], | ||
); | ||
|
||
const cancelSelected = useCallback(() => { | ||
setActiveInfo(""); | ||
shareScreenStore.updateIsDisplayScreen(null); | ||
shareScreenStore.updateScreenID(null); | ||
}, [shareScreenStore]); | ||
|
||
return ( | ||
<div className="screen-list"> | ||
{screenInfo.displayList.map(info => { | ||
const key = `display-${info.displayId.id}`; | ||
const isActive = activeInfo === key; | ||
|
||
return ( | ||
<ScreenItem | ||
info={info} | ||
handleClick={isActive ? cancelSelected : onClick} | ||
active={isActive} | ||
key={key} | ||
/> | ||
); | ||
})} | ||
{screenInfo.windowList.map(info => { | ||
const key = `window-${info.windowId}`; | ||
const isActive = activeInfo === key; | ||
|
||
return ( | ||
<ScreenItem | ||
info={info} | ||
handleClick={isActive ? cancelSelected : onClick} | ||
active={activeInfo === key} | ||
key={key} | ||
/> | ||
); | ||
})} | ||
</div> | ||
); | ||
}); | ||
|
||
interface ScreenItemProps { | ||
info: ScreenInfo["windowList"][0] | ScreenInfo["displayList"][0]; | ||
handleClick: (isDisplay: boolean, id: number) => void; | ||
active: boolean; | ||
} | ||
|
||
const ScreenItem = observer<ScreenItemProps>(function ScreenItem({ info, handleClick, active }) { | ||
const screenInfo = getScreenInfo(info); | ||
|
||
return ( | ||
<> | ||
<div className="screen-item"> | ||
<div | ||
className={classNames("screen-image-box", { | ||
active, | ||
})} | ||
onClick={() => handleClick(screenInfo.isDisplay, screenInfo.id)} | ||
> | ||
<img src={uint8ArrayToImageURL(info.image)} alt="screenshots" /> | ||
</div> | ||
<span>{screenInfo.name}</span> | ||
</div> | ||
</> | ||
); | ||
}); |
Oops, something went wrong.