mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-13 19:00:09 +00:00
qfix: add support for recording videos from desktop (#8306)
This commit is contained in:
parent
e70c86f86f
commit
c12fee1bb2
@ -115,6 +115,7 @@ function hookOpenWindow (window: BrowserWindow): void {
|
|||||||
devTools: true,
|
devTools: true,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
partition: sessionPartition,
|
partition: sessionPartition,
|
||||||
|
nodeIntegration: true,
|
||||||
preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js'),
|
preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js'),
|
||||||
additionalArguments: [
|
additionalArguments: [
|
||||||
`--open=${encodeURI(
|
`--open=${encodeURI(
|
||||||
@ -192,6 +193,7 @@ const createWindow = async (): Promise<void> => {
|
|||||||
webPreferences: {
|
webPreferences: {
|
||||||
devTools: true,
|
devTools: true,
|
||||||
sandbox: false,
|
sandbox: false,
|
||||||
|
nodeIntegration: true,
|
||||||
backgroundThrottling: false,
|
backgroundThrottling: false,
|
||||||
partition: sessionPartition,
|
partition: sessionPartition,
|
||||||
preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js')
|
preload: path.join(app.getAppPath(), 'dist', 'main', 'preload.js')
|
||||||
|
@ -19,10 +19,11 @@ import { workbenchId, logOut } from '@hcengineering/workbench'
|
|||||||
|
|
||||||
import { isOwnerOrMaintainer } from '@hcengineering/core'
|
import { isOwnerOrMaintainer } from '@hcengineering/core'
|
||||||
import { configurePlatform } from './platform'
|
import { configurePlatform } from './platform'
|
||||||
import { defineScreenShare } from './screenShare'
|
import { defineScreenShare, defineScreenRecorder } from './screenShare'
|
||||||
import { IPCMainExposed } from './types'
|
import { IPCMainExposed } from './types'
|
||||||
|
|
||||||
defineScreenShare()
|
defineScreenShare()
|
||||||
|
defineScreenRecorder()
|
||||||
|
|
||||||
void configurePlatform().then(() => {
|
void configurePlatform().then(() => {
|
||||||
createApp(document.body)
|
createApp(document.body)
|
||||||
|
@ -5,6 +5,54 @@ import { showPopup } from '@hcengineering/ui'
|
|||||||
import { Track, LocalTrack, LocalAudioTrack, LocalVideoTrack, ParticipantEvent, TrackInvalidError, ScreenShareCaptureOptions, DeviceUnsupportedError, ScreenSharePresets } from 'livekit-client'
|
import { Track, LocalTrack, LocalAudioTrack, LocalVideoTrack, ParticipantEvent, TrackInvalidError, ScreenShareCaptureOptions, DeviceUnsupportedError, ScreenSharePresets } from 'livekit-client'
|
||||||
|
|
||||||
import { IPCMainExposed } from './types'
|
import { IPCMainExposed } from './types'
|
||||||
|
import { setMetadata } from '@hcengineering/platform'
|
||||||
|
import recordPlugin from '@hcengineering/recorder'
|
||||||
|
|
||||||
|
export async function getMediaStream (opts?: DisplayMediaStreamOptions): Promise<MediaStream> {
|
||||||
|
if (opts === undefined) {
|
||||||
|
throw new Error('opts must be provided')
|
||||||
|
}
|
||||||
|
const ipcMain = (window as any).electron as IPCMainExposed
|
||||||
|
const sources = await ipcMain.getScreenSources()
|
||||||
|
|
||||||
|
const hasAccess = await ipcMain.getScreenAccess()
|
||||||
|
if (!hasAccess) {
|
||||||
|
log.error('No screen access granted')
|
||||||
|
throw new Error('No screen access granted')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.mediaDevices.getDisplayMedia === undefined) {
|
||||||
|
throw new DeviceUnsupportedError('getDisplayMedia not supported')
|
||||||
|
}
|
||||||
|
return await new Promise<MediaStream>((resolve, reject) => {
|
||||||
|
showPopup(
|
||||||
|
love.component.SelectScreenSourcePopup,
|
||||||
|
{
|
||||||
|
sources
|
||||||
|
},
|
||||||
|
'top',
|
||||||
|
() => {
|
||||||
|
reject(new Error('No source selected'))
|
||||||
|
},
|
||||||
|
(val) => {
|
||||||
|
if (val != null) {
|
||||||
|
opts.video = {
|
||||||
|
mandatory: {
|
||||||
|
...(typeof opts.video === 'boolean' ? {} : opts.video),
|
||||||
|
chromeMediaSource: 'desktop',
|
||||||
|
chromeMediaSourceId: val
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
resolve(window.navigator.mediaDevices.getUserMedia(opts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defineScreenRecorder (): void {
|
||||||
|
setMetadata(recordPlugin.metadata.GetCustomMediaStream, getMediaStream)
|
||||||
|
}
|
||||||
|
|
||||||
export function defineScreenShare (): void {
|
export function defineScreenShare (): void {
|
||||||
setCustomCreateScreenTracks(async function electronCreateScreenTracks (options?: ScreenShareCaptureOptions) {
|
setCustomCreateScreenTracks(async function electronCreateScreenTracks (options?: ScreenShareCaptureOptions) {
|
||||||
|
@ -91,8 +91,10 @@
|
|||||||
if (state === RecordingState.Inactive) {
|
if (state === RecordingState.Inactive) {
|
||||||
try {
|
try {
|
||||||
await createScreenRecorder()
|
await createScreenRecorder()
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
console.log('cant create screen recorder', err)
|
||||||
distpacher('close', true)
|
distpacher('close', true)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
await showCountdown()
|
await showCountdown()
|
||||||
recorder?.start()
|
recorder?.start()
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
import { type Resources } from '@hcengineering/platform'
|
import { type Resources } from '@hcengineering/platform'
|
||||||
import { record } from './recording'
|
import { record } from './recording'
|
||||||
|
|
||||||
|
export { ScreenRecorder } from './screen-recorder'
|
||||||
|
|
||||||
export default async (): Promise<Resources> => ({
|
export default async (): Promise<Resources> => ({
|
||||||
function: {
|
function: {
|
||||||
Record: record
|
Record: record
|
||||||
|
@ -13,8 +13,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import plugin from '@hcengineering/recorder'
|
||||||
import { Recorder } from './recorder'
|
import { Recorder } from './recorder'
|
||||||
import { TusUploader, type Uploader, type Options } from './uploader'
|
import { TusUploader, type Uploader, type Options } from './uploader'
|
||||||
|
import { getMetadata } from '@hcengineering/platform'
|
||||||
|
|
||||||
export class ScreenRecorder {
|
export class ScreenRecorder {
|
||||||
private readonly recorder: Recorder
|
private readonly recorder: Recorder
|
||||||
@ -29,11 +31,20 @@ export class ScreenRecorder {
|
|||||||
let width = 0
|
let width = 0
|
||||||
let height = 0
|
let height = 0
|
||||||
const combinedStream = new MediaStream()
|
const combinedStream = new MediaStream()
|
||||||
const displayStream = await navigator.mediaDevices.getDisplayMedia({
|
const getMediaStream =
|
||||||
video: { frameRate: opts.fps ?? 30 },
|
getMetadata(plugin.metadata.GetCustomMediaStream) ??
|
||||||
audio: true
|
(async (op) => await navigator.mediaDevices.getDisplayMedia(op))
|
||||||
|
const displayStream = await getMediaStream({
|
||||||
|
video: { frameRate: opts.fps ?? 30 }
|
||||||
})
|
})
|
||||||
const microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
try {
|
||||||
|
const microphoneStream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
|
microphoneStream.getAudioTracks().forEach((track) => {
|
||||||
|
combinedStream.addTrack(track)
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('microphone is disabled', err)
|
||||||
|
}
|
||||||
displayStream.getVideoTracks().forEach((track) => {
|
displayStream.getVideoTracks().forEach((track) => {
|
||||||
combinedStream.addTrack(track)
|
combinedStream.addTrack(track)
|
||||||
width = Math.max(track.getSettings().width ?? width, width)
|
width = Math.max(track.getSettings().width ?? width, width)
|
||||||
@ -42,9 +53,6 @@ export class ScreenRecorder {
|
|||||||
displayStream.getAudioTracks().forEach((track) => {
|
displayStream.getAudioTracks().forEach((track) => {
|
||||||
combinedStream.addTrack(track)
|
combinedStream.addTrack(track)
|
||||||
})
|
})
|
||||||
microphoneStream.getAudioTracks().forEach((track) => {
|
|
||||||
combinedStream.addTrack(track)
|
|
||||||
})
|
|
||||||
|
|
||||||
const recorder = new Recorder(combinedStream)
|
const recorder = new Recorder(combinedStream)
|
||||||
const uploader = new TusUploader(recorder.asStream(), { ...opts, metadata: { resolution: width + ':' + height } })
|
const uploader = new TusUploader(recorder.asStream(), { ...opts, metadata: { resolution: width + ':' + height } })
|
||||||
|
@ -19,6 +19,11 @@ import { type UploadHandler } from '@hcengineering/uploader'
|
|||||||
*/
|
*/
|
||||||
export const recorderId = 'recorder' as Plugin
|
export const recorderId = 'recorder' as Plugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export type GetMediaStream = (options?: DisplayMediaStreamOptions) => Promise<MediaStream>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
@ -27,7 +32,8 @@ const recordPlugin = plugin(recorderId, {
|
|||||||
Record: '' as Asset
|
Record: '' as Asset
|
||||||
},
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
StreamUrl: '' as Metadata<string>
|
StreamUrl: '' as Metadata<string>,
|
||||||
|
GetCustomMediaStream: '' as Metadata<GetMediaStream>
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
Pause: '' as IntlString,
|
Pause: '' as IntlString,
|
||||||
|
Loading…
Reference in New Issue
Block a user