Add experimental support for sharing desktop
This commit is contained in:
parent
ee209d7889
commit
61fc53bcf9
@ -32,6 +32,9 @@ window.navigator.mediaDevices.enumerateDevices = async () => {
|
|||||||
window.navigator.mediaDevices.getUserMedia = async () => {
|
window.navigator.mediaDevices.getUserMedia = async () => {
|
||||||
return {} as any
|
return {} as any
|
||||||
}
|
}
|
||||||
|
(window.navigator.mediaDevices as any).getDisplayMedia = async () => {
|
||||||
|
return {} as any
|
||||||
|
}
|
||||||
|
|
||||||
export const play = jest.fn()
|
export const play = jest.fn()
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { makeAction, AsyncAction } from '../async'
|
import { makeAction, AsyncAction } from '../async'
|
||||||
import { MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_ENUMERATE, MEDIA_STREAM } from '../constants'
|
import { MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_ENUMERATE, MEDIA_STREAM, ME, ME_DESKTOP } from '../constants'
|
||||||
import _debug from 'debug'
|
import _debug from 'debug'
|
||||||
|
|
||||||
const debug = _debug('peercalls')
|
const debug = _debug('peercalls')
|
||||||
@ -66,6 +66,11 @@ async function getUserMedia(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getDisplayMedia(): Promise<MediaStream> {
|
||||||
|
const mediaDevices = navigator.mediaDevices as any // eslint-disable-line
|
||||||
|
return mediaDevices.getDisplayMedia({video: true, audio: false})
|
||||||
|
}
|
||||||
|
|
||||||
export interface MediaVideoConstraintAction {
|
export interface MediaVideoConstraintAction {
|
||||||
type: 'MEDIA_VIDEO_CONSTRAINT_SET'
|
type: 'MEDIA_VIDEO_CONSTRAINT_SET'
|
||||||
payload: VideoConstraint
|
payload: VideoConstraint
|
||||||
@ -106,12 +111,33 @@ export const getMediaStream = makeAction(
|
|||||||
MEDIA_STREAM,
|
MEDIA_STREAM,
|
||||||
async (constraints: GetMediaConstraints) => {
|
async (constraints: GetMediaConstraints) => {
|
||||||
debug('getMediaStream', constraints)
|
debug('getMediaStream', constraints)
|
||||||
return getUserMedia(constraints)
|
const payload: MediaStreamPayload = {
|
||||||
|
stream: await getUserMedia(constraints),
|
||||||
|
userId: ME,
|
||||||
|
}
|
||||||
|
return payload
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const getDesktopStream = makeAction(
|
||||||
|
MEDIA_STREAM,
|
||||||
|
async () => {
|
||||||
|
debug('getDesktopStream')
|
||||||
|
const payload: MediaStreamPayload = {
|
||||||
|
stream: await getDisplayMedia(),
|
||||||
|
userId: ME_DESKTOP,
|
||||||
|
}
|
||||||
|
return payload
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface MediaStreamPayload {
|
||||||
|
stream: MediaStream
|
||||||
|
userId: string
|
||||||
|
}
|
||||||
|
|
||||||
export type MediaEnumerateAction = AsyncAction<'MEDIA_ENUMERATE', MediaDevice[]>
|
export type MediaEnumerateAction = AsyncAction<'MEDIA_ENUMERATE', MediaDevice[]>
|
||||||
export type MediaStreamAction = AsyncAction<'MEDIA_STREAM', MediaStream>
|
export type MediaStreamAction = AsyncAction<'MEDIA_STREAM', MediaStreamPayload>
|
||||||
export type MediaPlayAction = AsyncAction<'MEDIA_PLAY', void>
|
export type MediaPlayAction = AsyncAction<'MEDIA_PLAY', void>
|
||||||
|
|
||||||
export type MediaAction =
|
export type MediaAction =
|
||||||
|
|||||||
@ -63,6 +63,10 @@ class PeerHandler {
|
|||||||
// we no longer automatically send the stream to the peer.
|
// we no longer automatically send the stream to the peer.
|
||||||
peer.addStream(localStream.stream)
|
peer.addStream(localStream.stream)
|
||||||
}
|
}
|
||||||
|
const desktopStream = state.streams[constants.ME_DESKTOP]
|
||||||
|
if (desktopStream && desktopStream.stream) {
|
||||||
|
peer.addStream(desktopStream.stream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
handleStream = (stream: MediaStream) => {
|
handleStream = (stream: MediaStream) => {
|
||||||
const { user, dispatch } = this
|
const { user, dispatch } = this
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import Notifications from './Notifications'
|
|||||||
import { Side } from './Side'
|
import { Side } from './Side'
|
||||||
import Toolbar from './Toolbar'
|
import Toolbar from './Toolbar'
|
||||||
import Video from './Video'
|
import Video from './Video'
|
||||||
|
import { getDesktopStream } from '../actions/MediaActions'
|
||||||
|
|
||||||
export interface AppProps {
|
export interface AppProps {
|
||||||
active: string | null
|
active: string | null
|
||||||
@ -25,6 +26,7 @@ export interface AppProps {
|
|||||||
play: () => void
|
play: () => void
|
||||||
sendMessage: (message: TextMessage) => void
|
sendMessage: (message: TextMessage) => void
|
||||||
streams: Record<string, AddStreamPayload>
|
streams: Record<string, AddStreamPayload>
|
||||||
|
getDesktopStream: typeof getDesktopStream
|
||||||
removeStream: typeof removeStream
|
removeStream: typeof removeStream
|
||||||
onSendFile: (file: File) => void
|
onSendFile: (file: File) => void
|
||||||
toggleActive: (userId: string) => void
|
toggleActive: (userId: string) => void
|
||||||
@ -35,6 +37,8 @@ export interface AppState {
|
|||||||
chatVisible: boolean
|
chatVisible: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localStreams: string[] = [constants.ME, constants.ME_DESKTOP]
|
||||||
|
|
||||||
export default class App extends React.PureComponent<AppProps, AppState> {
|
export default class App extends React.PureComponent<AppProps, AppState> {
|
||||||
state: AppState = {
|
state: AppState = {
|
||||||
videos: {},
|
videos: {},
|
||||||
@ -61,6 +65,7 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
}
|
}
|
||||||
onHangup = () => {
|
onHangup = () => {
|
||||||
this.props.removeStream(constants.ME)
|
this.props.removeStream(constants.ME)
|
||||||
|
this.props.removeStream(constants.ME_DESKTOP)
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
@ -93,6 +98,9 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
onSendFile={onSendFile}
|
onSendFile={onSendFile}
|
||||||
onHangup={this.onHangup}
|
onHangup={this.onHangup}
|
||||||
stream={streams[constants.ME]}
|
stream={streams[constants.ME]}
|
||||||
|
desktopStream={streams[constants.ME_DESKTOP]}
|
||||||
|
onGetDesktopStream={this.props.getDesktopStream}
|
||||||
|
onRemoveStream={this.props.removeStream}
|
||||||
/>
|
/>
|
||||||
</Side>
|
</Side>
|
||||||
<Side className={chatVisibleClassName} top zIndex={1}>
|
<Side className={chatVisibleClassName} top zIndex={1}>
|
||||||
@ -109,19 +117,19 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
visible={this.state.chatVisible}
|
visible={this.state.chatVisible}
|
||||||
/>
|
/>
|
||||||
<div className={classnames('videos', chatVisibleClassName)}>
|
<div className={classnames('videos', chatVisibleClassName)}>
|
||||||
{streams[constants.ME] && (
|
{localStreams.map(userId => (
|
||||||
<Video
|
<Video
|
||||||
videos={videos}
|
videos={videos}
|
||||||
active={active === constants.ME}
|
key={userId}
|
||||||
|
active={active === userId}
|
||||||
onClick={toggleActive}
|
onClick={toggleActive}
|
||||||
play={play}
|
play={play}
|
||||||
stream={streams[constants.ME]}
|
stream={streams[userId]}
|
||||||
userId={constants.ME}
|
userId={userId}
|
||||||
muted
|
muted
|
||||||
mirrored
|
mirrored
|
||||||
/>
|
/>
|
||||||
)}
|
))}
|
||||||
|
|
||||||
{
|
{
|
||||||
map(peers, (_, userId) => userId)
|
map(peers, (_, userId) => userId)
|
||||||
.filter(stream => !!stream)
|
.filter(stream => !!stream)
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import ReactDOM from 'react-dom'
|
|||||||
import TestUtils from 'react-dom/test-utils'
|
import TestUtils from 'react-dom/test-utils'
|
||||||
import Toolbar, { ToolbarProps } from './Toolbar'
|
import Toolbar, { ToolbarProps } from './Toolbar'
|
||||||
import { MediaStream } from '../window'
|
import { MediaStream } from '../window'
|
||||||
import { AddStreamPayload } from '../actions/StreamActions'
|
import { AddStreamPayload, removeStream } from '../actions/StreamActions'
|
||||||
|
import { ME_DESKTOP } from '../constants'
|
||||||
|
import { getDesktopStream } from '../actions/MediaActions'
|
||||||
|
|
||||||
describe('components/Toolbar', () => {
|
describe('components/Toolbar', () => {
|
||||||
|
|
||||||
@ -15,15 +17,19 @@ describe('components/Toolbar', () => {
|
|||||||
class ToolbarWrapper extends React.PureComponent<ToolbarProps, StreamState> {
|
class ToolbarWrapper extends React.PureComponent<ToolbarProps, StreamState> {
|
||||||
state = {
|
state = {
|
||||||
stream: null,
|
stream: null,
|
||||||
|
desktopStream: null,
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
return <Toolbar
|
return <Toolbar
|
||||||
chatVisible={this.props.chatVisible}
|
chatVisible={this.props.chatVisible}
|
||||||
onToggleChat={this.props.onToggleChat}
|
onToggleChat={this.props.onToggleChat}
|
||||||
onHangup={this.props.onHangup}
|
onHangup={this.props.onHangup}
|
||||||
|
onGetDesktopStream={this.props.onGetDesktopStream}
|
||||||
|
onRemoveStream={this.props.onRemoveStream}
|
||||||
onSendFile={this.props.onSendFile}
|
onSendFile={this.props.onSendFile}
|
||||||
messagesCount={this.props.messagesCount}
|
messagesCount={this.props.messagesCount}
|
||||||
stream={this.state.stream || this.props.stream}
|
stream={this.state.stream || this.props.stream}
|
||||||
|
desktopStream={this.state.desktopStream || this.props.desktopStream}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,11 +40,16 @@ describe('components/Toolbar', () => {
|
|||||||
let onToggleChat: jest.Mock<() => void>
|
let onToggleChat: jest.Mock<() => void>
|
||||||
let onSendFile: jest.Mock<(file: File) => void>
|
let onSendFile: jest.Mock<(file: File) => void>
|
||||||
let onHangup: jest.Mock<() => void>
|
let onHangup: jest.Mock<() => void>
|
||||||
|
let onGetDesktopStream: jest.MockedFunction<typeof getDesktopStream>
|
||||||
|
let onRemoveStream: jest.MockedFunction<typeof removeStream>
|
||||||
|
let desktopStream: AddStreamPayload | undefined
|
||||||
async function render () {
|
async function render () {
|
||||||
mediaStream = new MediaStream()
|
mediaStream = new MediaStream()
|
||||||
onToggleChat = jest.fn()
|
onToggleChat = jest.fn()
|
||||||
onSendFile = jest.fn()
|
onSendFile = jest.fn()
|
||||||
onHangup = jest.fn()
|
onHangup = jest.fn()
|
||||||
|
onGetDesktopStream = jest.fn()
|
||||||
|
onRemoveStream = jest.fn()
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
await new Promise<ToolbarWrapper>(resolve => {
|
await new Promise<ToolbarWrapper>(resolve => {
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
@ -50,6 +61,9 @@ describe('components/Toolbar', () => {
|
|||||||
onSendFile={onSendFile}
|
onSendFile={onSendFile}
|
||||||
messagesCount={1}
|
messagesCount={1}
|
||||||
stream={{ userId: '', stream: mediaStream, url }}
|
stream={{ userId: '', stream: mediaStream, url }}
|
||||||
|
desktopStream={desktopStream}
|
||||||
|
onGetDesktopStream={onGetDesktopStream}
|
||||||
|
onRemoveStream={onRemoveStream}
|
||||||
/>,
|
/>,
|
||||||
div,
|
div,
|
||||||
)
|
)
|
||||||
@ -136,4 +150,26 @@ describe('components/Toolbar', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('desktop sharing', () => {
|
||||||
|
it('starts desktop sharing', async () => {
|
||||||
|
const shareDesktop = node.querySelector('.stream-desktop')!
|
||||||
|
expect(shareDesktop).toBeDefined()
|
||||||
|
TestUtils.Simulate.click(shareDesktop)
|
||||||
|
await Promise.resolve()
|
||||||
|
expect(onGetDesktopStream.mock.calls.length).toBe(1)
|
||||||
|
})
|
||||||
|
it('stops desktop sharing', async () => {
|
||||||
|
desktopStream = {
|
||||||
|
userId: ME_DESKTOP,
|
||||||
|
stream: new MediaStream(),
|
||||||
|
}
|
||||||
|
await render()
|
||||||
|
const shareDesktop = node.querySelector('.share-desktop')!
|
||||||
|
expect(shareDesktop).toBeDefined()
|
||||||
|
TestUtils.Simulate.click(shareDesktop)
|
||||||
|
await Promise.resolve()
|
||||||
|
expect(onRemoveStream.mock.calls.length).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import screenfull from 'screenfull'
|
import screenfull from 'screenfull'
|
||||||
import { AddStreamPayload } from '../actions/StreamActions'
|
import { AddStreamPayload, removeStream } from '../actions/StreamActions'
|
||||||
|
import { ME_DESKTOP } from '../constants'
|
||||||
|
import { getDesktopStream } from '../actions/MediaActions'
|
||||||
|
|
||||||
const hidden = {
|
const hidden = {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
@ -10,7 +12,10 @@ const hidden = {
|
|||||||
export interface ToolbarProps {
|
export interface ToolbarProps {
|
||||||
messagesCount: number
|
messagesCount: number
|
||||||
stream: AddStreamPayload
|
stream: AddStreamPayload
|
||||||
|
desktopStream: AddStreamPayload | undefined
|
||||||
onToggleChat: () => void
|
onToggleChat: () => void
|
||||||
|
onGetDesktopStream: typeof getDesktopStream
|
||||||
|
onRemoveStream: typeof removeStream
|
||||||
onSendFile: (file: File) => void
|
onSendFile: (file: File) => void
|
||||||
onHangup: () => void
|
onHangup: () => void
|
||||||
chatVisible: boolean
|
chatVisible: boolean
|
||||||
@ -56,6 +61,7 @@ function ToolbarButton(props: ToolbarButtonProps) {
|
|||||||
export default class Toolbar
|
export default class Toolbar
|
||||||
extends React.PureComponent<ToolbarProps, ToolbarState> {
|
extends React.PureComponent<ToolbarProps, ToolbarState> {
|
||||||
file = React.createRef<HTMLInputElement>()
|
file = React.createRef<HTMLInputElement>()
|
||||||
|
desktopStream: MediaStream | undefined
|
||||||
|
|
||||||
constructor(props: ToolbarProps) {
|
constructor(props: ToolbarProps) {
|
||||||
super(props)
|
super(props)
|
||||||
@ -113,6 +119,13 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
|
|||||||
})
|
})
|
||||||
this.props.onToggleChat()
|
this.props.onToggleChat()
|
||||||
}
|
}
|
||||||
|
handleToggleShareDesktop = () => {
|
||||||
|
if (this.props.desktopStream) {
|
||||||
|
this.props.onRemoveStream(ME_DESKTOP)
|
||||||
|
} else {
|
||||||
|
this.props.onGetDesktopStream()
|
||||||
|
}
|
||||||
|
}
|
||||||
render () {
|
render () {
|
||||||
const { messagesCount, stream } = this.props
|
const { messagesCount, stream } = this.props
|
||||||
const unreadCount = messagesCount - this.state.readMessages
|
const unreadCount = messagesCount - this.state.readMessages
|
||||||
@ -145,6 +158,14 @@ extends React.PureComponent<ToolbarProps, ToolbarState> {
|
|||||||
title='Send File'
|
title='Send File'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ToolbarButton
|
||||||
|
className='stream-desktop'
|
||||||
|
icon='icon-display'
|
||||||
|
onClick={this.handleToggleShareDesktop}
|
||||||
|
on={!!this.desktopStream}
|
||||||
|
title='Share Desktop'
|
||||||
|
/>
|
||||||
|
|
||||||
{stream && (
|
{stream && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
|
|||||||
@ -8,6 +8,7 @@ export const ALERT_CLEAR = 'ALERT_CLEAR'
|
|||||||
export const INIT = 'INIT'
|
export const INIT = 'INIT'
|
||||||
|
|
||||||
export const ME = '_me_'
|
export const ME = '_me_'
|
||||||
|
export const ME_DESKTOP = '_me_desktop'
|
||||||
|
|
||||||
export const NOTIFY = 'NOTIFY'
|
export const NOTIFY = 'NOTIFY'
|
||||||
export const NOTIFY_DISMISS = 'NOTIFY_DISMISS'
|
export const NOTIFY_DISMISS = 'NOTIFY_DISMISS'
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { connect } from 'react-redux'
|
import { connect } from 'react-redux'
|
||||||
import { init } from '../actions/CallActions'
|
import { init } from '../actions/CallActions'
|
||||||
import { play } from '../actions/MediaActions'
|
import { getDesktopStream, play } from '../actions/MediaActions'
|
||||||
import { dismissNotification } from '../actions/NotifyActions'
|
import { dismissNotification } from '../actions/NotifyActions'
|
||||||
import { sendFile, sendMessage } from '../actions/PeerActions'
|
import { sendFile, sendMessage } from '../actions/PeerActions'
|
||||||
import { toggleActive, removeStream } from '../actions/StreamActions'
|
import { toggleActive, removeStream } from '../actions/StreamActions'
|
||||||
@ -22,6 +22,7 @@ const mapDispatchToProps = {
|
|||||||
toggleActive,
|
toggleActive,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
dismissNotification,
|
dismissNotification,
|
||||||
|
getDesktopStream,
|
||||||
removeStream,
|
removeStream,
|
||||||
init,
|
init,
|
||||||
onSendFile: sendFile,
|
onSendFile: sendFile,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import * as MediaActions from '../actions/MediaActions'
|
import * as MediaActions from '../actions/MediaActions'
|
||||||
import { MEDIA_ENUMERATE, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_STREAM, ME, PEERS_DESTROY, PEER_ADD } from '../constants'
|
import { MEDIA_ENUMERATE, MEDIA_VIDEO_CONSTRAINT_SET, MEDIA_AUDIO_CONSTRAINT_SET, MEDIA_STREAM, ME, PEERS_DESTROY, PEER_ADD, ME_DESKTOP } from '../constants'
|
||||||
import { createStore, Store } from '../store'
|
import { createStore, Store } from '../store'
|
||||||
import SimplePeer from 'simple-peer'
|
import SimplePeer from 'simple-peer'
|
||||||
|
|
||||||
@ -97,11 +97,12 @@ describe('media', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function dispatch() {
|
async function dispatch() {
|
||||||
const promise = store.dispatch(MediaActions.getMediaStream({
|
const result = await store.dispatch(MediaActions.getMediaStream({
|
||||||
audio: true,
|
audio: true,
|
||||||
video: true,
|
video: true,
|
||||||
}))
|
}))
|
||||||
expect(await promise).toBe(stream)
|
expect(result.stream).toBe(stream)
|
||||||
|
expect(result.userId).toBe(ME)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('reducers/streams', () => {
|
describe('reducers/streams', () => {
|
||||||
@ -148,7 +149,7 @@ describe('media', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('replaces local stream on all peers', async () => {
|
it('replaces local camera stream on all peers', async () => {
|
||||||
await dispatch()
|
await dispatch()
|
||||||
peers.forEach(peer => {
|
peers.forEach(peer => {
|
||||||
expect((peer.addStream as jest.Mock).mock.calls)
|
expect((peer.addStream as jest.Mock).mock.calls)
|
||||||
@ -183,10 +184,30 @@ describe('media', () => {
|
|||||||
})
|
})
|
||||||
expect(promise.type).toBe('MEDIA_STREAM')
|
expect(promise.type).toBe('MEDIA_STREAM')
|
||||||
expect(promise.status).toBe('pending')
|
expect(promise.status).toBe('pending')
|
||||||
expect(await promise).toBe(stream)
|
const result = await promise
|
||||||
|
expect(result.stream).toBe(stream)
|
||||||
|
expect(result.userId).toBe(ME)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('getDesktopStream (getDisplayMedia)', () => {
|
||||||
|
const stream: MediaStream = {} as MediaStream
|
||||||
|
beforeEach(() => {
|
||||||
|
(navigator.mediaDevices as any).getDisplayMedia = async () => stream
|
||||||
|
})
|
||||||
|
async function dispatch() {
|
||||||
|
const result = await store.dispatch(MediaActions.getDesktopStream())
|
||||||
|
expect(result.stream).toBe(stream)
|
||||||
|
expect(result.userId).toBe(ME_DESKTOP)
|
||||||
|
}
|
||||||
|
it('adds the local stream to the map of videos', async () => {
|
||||||
|
expect(store.getState().streams[ME_DESKTOP]).toBeFalsy()
|
||||||
|
await dispatch()
|
||||||
|
expect(store.getState().streams[ME_DESKTOP]).toBeTruthy()
|
||||||
|
expect(store.getState().streams[ME_DESKTOP].stream).toBe(stream)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
@ -9,7 +9,7 @@ export type PeersState = Record<string, Peer.Instance>
|
|||||||
|
|
||||||
const defaultState: PeersState = {}
|
const defaultState: PeersState = {}
|
||||||
|
|
||||||
let localStream: MediaStream | undefined
|
let localStreams: Record<string, MediaStream> = {}
|
||||||
|
|
||||||
export default function peers(
|
export default function peers(
|
||||||
state = defaultState,
|
state = defaultState,
|
||||||
@ -24,16 +24,18 @@ export default function peers(
|
|||||||
case constants.PEER_REMOVE:
|
case constants.PEER_REMOVE:
|
||||||
return omit(state, [action.payload.userId])
|
return omit(state, [action.payload.userId])
|
||||||
case constants.PEERS_DESTROY:
|
case constants.PEERS_DESTROY:
|
||||||
localStream = undefined
|
localStreams = {}
|
||||||
forEach(state, peer => peer.destroy())
|
forEach(state, peer => peer.destroy())
|
||||||
return defaultState
|
return defaultState
|
||||||
case constants.MEDIA_STREAM:
|
case constants.MEDIA_STREAM:
|
||||||
if (action.status === 'resolved') {
|
if (action.status === 'resolved') {
|
||||||
|
// userId can be ME or ME_DESKTOP
|
||||||
forEach(state, peer => {
|
forEach(state, peer => {
|
||||||
|
const localStream = localStreams[action.payload.userId]
|
||||||
localStream && peer.removeStream(localStream)
|
localStream && peer.removeStream(localStream)
|
||||||
peer.addStream(action.payload)
|
peer.addStream(action.payload.stream)
|
||||||
})
|
})
|
||||||
localStream = action.payload
|
localStreams[action.payload.userId] = action.payload.stream
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import _debug from 'debug'
|
import _debug from 'debug'
|
||||||
import omit from 'lodash/omit'
|
import omit from 'lodash/omit'
|
||||||
import { AddStreamAction, AddStreamPayload, RemoveStreamAction, StreamAction } from '../actions/StreamActions'
|
import { AddStreamAction, AddStreamPayload, RemoveStreamAction, StreamAction } from '../actions/StreamActions'
|
||||||
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM, ME } from '../constants'
|
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM } from '../constants'
|
||||||
import { createObjectURL, revokeObjectURL } from '../window'
|
import { createObjectURL, revokeObjectURL } from '../window'
|
||||||
import { MediaStreamAction } from '../actions/MediaActions'
|
import { MediaStreamPayload, MediaStreamAction } from '../actions/MediaActions'
|
||||||
|
|
||||||
const debug = _debug('peercalls')
|
const debug = _debug('peercalls')
|
||||||
const defaultState = Object.freeze({})
|
const defaultState = Object.freeze({})
|
||||||
@ -52,14 +52,14 @@ function removeStream (
|
|||||||
return omit(state, [userId])
|
return omit(state, [userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
function replaceStream(state: StreamsState, stream: MediaStream): StreamsState {
|
function replaceStream(
|
||||||
|
state: StreamsState,
|
||||||
|
payload: MediaStreamPayload,
|
||||||
|
): StreamsState {
|
||||||
state = removeStream(state, {
|
state = removeStream(state, {
|
||||||
userId: ME,
|
userId: payload.userId,
|
||||||
})
|
|
||||||
return addStream(state, {
|
|
||||||
userId: ME,
|
|
||||||
stream,
|
|
||||||
})
|
})
|
||||||
|
return addStream(state, payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function streams(
|
export default function streams(
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'icons';
|
font-family: 'icons';
|
||||||
src: url('../res/fonts/icons.eot?tcgv6b');
|
src: url('../res/fonts/icons.eot?ny6drs');
|
||||||
src: url('../res/fonts/icons.eot?tcgv6b#iefix') format('embedded-opentype'),
|
src: url('../res/fonts/icons.eot?ny6drs#iefix') format('embedded-opentype'),
|
||||||
url('../res/fonts/icons.woff?tcgv6b') format('woff'),
|
url('../res/fonts/icons.ttf?ny6drs') format('truetype'),
|
||||||
url('../res/fonts/icons.ttf?tcgv6b') format('truetype'),
|
url('../res/fonts/icons.woff?ny6drs') format('woff'),
|
||||||
url('../res/fonts/icons.svg?tcgv6b#icons') format('svg');
|
url('../res/fonts/icons.svg?ny6drs#icons') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user