Remove each user stream individually
This commit is contained in:
parent
f056048d62
commit
46a0b1f7ea
@ -68,9 +68,20 @@ class PeerHandler {
|
|||||||
}
|
}
|
||||||
handleTrack = (track: MediaStreamTrack, stream: MediaStream) => {
|
handleTrack = (track: MediaStreamTrack, stream: MediaStream) => {
|
||||||
const { user, dispatch } = this
|
const { user, dispatch } = this
|
||||||
debug('peer: %s, track', user.id)
|
const userId = user.id
|
||||||
|
debug('peer: %s, track', userId)
|
||||||
|
// Listen to mute event to know when a track was removed
|
||||||
|
// https://github.com/feross/simple-peer/issues/512
|
||||||
|
track.onmute = () => {
|
||||||
|
debug('peer: %s, track muted', userId)
|
||||||
|
dispatch(StreamActions.removeTrack({
|
||||||
|
userId,
|
||||||
|
stream,
|
||||||
|
track,
|
||||||
|
}))
|
||||||
|
}
|
||||||
dispatch(StreamActions.addStream({
|
dispatch(StreamActions.addStream({
|
||||||
userId: user.id,
|
userId,
|
||||||
stream,
|
stream,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -97,10 +108,13 @@ class PeerHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
handleClose = () => {
|
handleClose = () => {
|
||||||
const { dispatch, user } = this
|
const { dispatch, user, getState } = this
|
||||||
debug('peer: %s, close', user.id)
|
|
||||||
dispatch(NotifyActions.error('Peer connection closed'))
|
dispatch(NotifyActions.error('Peer connection closed'))
|
||||||
dispatch(StreamActions.removeStream(user.id))
|
const state = getState()
|
||||||
|
const userStreams = state.streams[user.id]
|
||||||
|
userStreams && userStreams.streams.forEach(s => {
|
||||||
|
dispatch(StreamActions.removeStream(user.id, s.stream))
|
||||||
|
})
|
||||||
dispatch(removePeer(user.id))
|
dispatch(removePeer(user.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,12 +20,16 @@ export interface RemoveStreamAction {
|
|||||||
|
|
||||||
export interface RemoveStreamPayload {
|
export interface RemoveStreamPayload {
|
||||||
userId: string
|
userId: string
|
||||||
stream?: MediaStream
|
stream: MediaStream
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetActiveStreamPayload {
|
||||||
|
userId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SetActiveStreamAction {
|
export interface SetActiveStreamAction {
|
||||||
type: 'ACTIVE_SET'
|
type: 'ACTIVE_SET'
|
||||||
payload: RemoveStreamPayload
|
payload: SetActiveStreamPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ToggleActiveStreamAction {
|
export interface ToggleActiveStreamAction {
|
||||||
@ -33,6 +37,17 @@ export interface ToggleActiveStreamAction {
|
|||||||
payload: UserIdPayload
|
payload: UserIdPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RemoveStreamTrackPayload {
|
||||||
|
userId: string
|
||||||
|
stream: MediaStream
|
||||||
|
track: MediaStreamTrack
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RemoveStreamTrackAction {
|
||||||
|
type: 'PEER_STREAM_TRACK_REMOVE'
|
||||||
|
payload: RemoveStreamTrackPayload
|
||||||
|
}
|
||||||
|
|
||||||
export interface UserIdPayload {
|
export interface UserIdPayload {
|
||||||
userId: string
|
userId: string
|
||||||
}
|
}
|
||||||
@ -44,12 +59,19 @@ export const addStream = (payload: AddStreamPayload): AddStreamAction => ({
|
|||||||
|
|
||||||
export const removeStream = (
|
export const removeStream = (
|
||||||
userId: string,
|
userId: string,
|
||||||
stream?: MediaStream,
|
stream: MediaStream,
|
||||||
): RemoveStreamAction => ({
|
): RemoveStreamAction => ({
|
||||||
type: constants.STREAM_REMOVE,
|
type: constants.STREAM_REMOVE,
|
||||||
payload: { userId, stream },
|
payload: { userId, stream },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const removeTrack = (
|
||||||
|
payload: RemoveStreamTrackPayload,
|
||||||
|
): RemoveStreamTrackAction => ({
|
||||||
|
type: constants.STREAM_TRACK_REMOVE,
|
||||||
|
payload,
|
||||||
|
})
|
||||||
|
|
||||||
export const setActive = (userId: string): SetActiveStreamAction => ({
|
export const setActive = (userId: string): SetActiveStreamAction => ({
|
||||||
type: constants.ACTIVE_SET,
|
type: constants.ACTIVE_SET,
|
||||||
payload: { userId },
|
payload: { userId },
|
||||||
@ -64,4 +86,5 @@ export type StreamAction =
|
|||||||
AddStreamAction |
|
AddStreamAction |
|
||||||
RemoveStreamAction |
|
RemoveStreamAction |
|
||||||
SetActiveStreamAction |
|
SetActiveStreamAction |
|
||||||
ToggleActiveStreamAction
|
ToggleActiveStreamAction |
|
||||||
|
RemoveStreamTrackAction
|
||||||
|
|||||||
@ -63,7 +63,16 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
init()
|
init()
|
||||||
}
|
}
|
||||||
onHangup = () => {
|
onHangup = () => {
|
||||||
this.props.removeStream(constants.ME)
|
const localStreams = this.getLocalStreams()
|
||||||
|
localStreams.streams.forEach(s => {
|
||||||
|
this.props.removeStream(constants.ME, s.stream)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
getLocalStreams() {
|
||||||
|
return this.props.streams[constants.ME] || {
|
||||||
|
userId: constants.ME,
|
||||||
|
streams: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
@ -86,10 +95,7 @@ export default class App extends React.PureComponent<AppProps, AppState> {
|
|||||||
'chat-visible': this.state.chatVisible,
|
'chat-visible': this.state.chatVisible,
|
||||||
})
|
})
|
||||||
|
|
||||||
const localStreams = streams[constants.ME] || {
|
const localStreams = this.getLocalStreams()
|
||||||
userId: constants.ME,
|
|
||||||
streams: [],
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
|
|||||||
@ -24,7 +24,6 @@ function mapStateToProps(state: State) {
|
|||||||
const hidden = !!localStream &&
|
const hidden = !!localStream &&
|
||||||
localStream.streams.filter(s => s.type === STREAM_TYPE_CAMERA).length > 0
|
localStream.streams.filter(s => s.type === STREAM_TYPE_CAMERA).length > 0
|
||||||
const visible = !hidden
|
const visible = !hidden
|
||||||
console.log('visible', visible)
|
|
||||||
return {
|
return {
|
||||||
...state.media,
|
...state.media,
|
||||||
visible,
|
visible,
|
||||||
|
|||||||
@ -37,6 +37,7 @@ export const SOCKET_EVENT_USERS = 'users'
|
|||||||
|
|
||||||
export const STREAM_ADD = 'PEER_STREAM_ADD'
|
export const STREAM_ADD = 'PEER_STREAM_ADD'
|
||||||
export const STREAM_REMOVE = 'PEER_STREAM_REMOVE'
|
export const STREAM_REMOVE = 'PEER_STREAM_REMOVE'
|
||||||
|
export const STREAM_TRACK_REMOVE = 'PEER_STREAM_TRACK_REMOVE'
|
||||||
|
|
||||||
export const STREAM_TYPE_CAMERA = 'camera'
|
export const STREAM_TYPE_CAMERA = 'camera'
|
||||||
export const STREAM_TYPE_DESKTOP = 'desktop'
|
export const STREAM_TYPE_DESKTOP = 'desktop'
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import Peer from 'simple-peer'
|
|||||||
import { PeerAction } from '../actions/PeerActions'
|
import { PeerAction } from '../actions/PeerActions'
|
||||||
import * as constants from '../constants'
|
import * as constants from '../constants'
|
||||||
import { MediaStreamAction } from '../actions/MediaActions'
|
import { MediaStreamAction } from '../actions/MediaActions'
|
||||||
|
import { RemoveStreamAction } from '../actions/StreamActions'
|
||||||
|
|
||||||
export type PeersState = Record<string, Peer.Instance>
|
export type PeersState = Record<string, Peer.Instance>
|
||||||
|
|
||||||
@ -11,9 +12,26 @@ const defaultState: PeersState = {}
|
|||||||
|
|
||||||
let localStreams: Record<string, MediaStream> = {}
|
let localStreams: Record<string, MediaStream> = {}
|
||||||
|
|
||||||
|
function handleRemoveStream(
|
||||||
|
state: PeersState,
|
||||||
|
action: RemoveStreamAction,
|
||||||
|
): PeersState {
|
||||||
|
const stream = action.payload.stream
|
||||||
|
if (action.payload.userId === constants.ME) {
|
||||||
|
forEach(state, peer => {
|
||||||
|
console.log('removing track from peer')
|
||||||
|
stream.getTracks().forEach(track => {
|
||||||
|
peer.removeTrack(track, stream)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
export default function peers(
|
export default function peers(
|
||||||
state = defaultState,
|
state = defaultState,
|
||||||
action: PeerAction | MediaStreamAction,
|
action: PeerAction | MediaStreamAction | RemoveStreamAction,
|
||||||
): PeersState {
|
): PeersState {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case constants.PEER_ADD:
|
case constants.PEER_ADD:
|
||||||
@ -27,6 +45,8 @@ export default function peers(
|
|||||||
localStreams = {}
|
localStreams = {}
|
||||||
forEach(state, peer => peer.destroy())
|
forEach(state, peer => peer.destroy())
|
||||||
return defaultState
|
return defaultState
|
||||||
|
case constants.STREAM_REMOVE:
|
||||||
|
return handleRemoveStream(state, action)
|
||||||
case constants.MEDIA_STREAM:
|
case constants.MEDIA_STREAM:
|
||||||
if (action.status === 'resolved') {
|
if (action.status === 'resolved') {
|
||||||
forEach(state, peer => {
|
forEach(state, peer => {
|
||||||
|
|||||||
@ -57,11 +57,11 @@ describe('reducers/alerts', () => {
|
|||||||
describe('removeStream', () => {
|
describe('removeStream', () => {
|
||||||
it('removes a stream', () => {
|
it('removes a stream', () => {
|
||||||
store.dispatch(StreamActions.addStream({ userId, stream }))
|
store.dispatch(StreamActions.addStream({ userId, stream }))
|
||||||
store.dispatch(StreamActions.removeStream(userId))
|
store.dispatch(StreamActions.removeStream(userId, stream))
|
||||||
expect(store.getState().streams).toEqual({})
|
expect(store.getState().streams).toEqual({})
|
||||||
})
|
})
|
||||||
it('does not fail when no stream', () => {
|
it('does not fail when no stream', () => {
|
||||||
store.dispatch(StreamActions.removeStream(userId))
|
store.dispatch(StreamActions.removeStream(userId, stream))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import _debug from 'debug'
|
import _debug from 'debug'
|
||||||
import omit from 'lodash/omit'
|
import omit from 'lodash/omit'
|
||||||
import { AddStreamAction, RemoveStreamAction, StreamAction, StreamType } from '../actions/StreamActions'
|
import { AddStreamAction, RemoveStreamAction, StreamAction, StreamType, RemoveStreamTrackAction } from '../actions/StreamActions'
|
||||||
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM } from '../constants'
|
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM, STREAM_TRACK_REMOVE } from '../constants'
|
||||||
import { createObjectURL, revokeObjectURL } from '../window'
|
import { createObjectURL, revokeObjectURL } from '../window'
|
||||||
import { MediaStreamAction } from '../actions/MediaActions'
|
import { MediaStreamAction } from '../actions/MediaActions'
|
||||||
|
|
||||||
@ -99,6 +99,27 @@ function removeStream (
|
|||||||
return omit(state, [userId])
|
return omit(state, [userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeStreamTrack(
|
||||||
|
state: StreamsState, payload: RemoveStreamTrackAction['payload'],
|
||||||
|
): StreamsState {
|
||||||
|
const { userId, stream, track } = payload
|
||||||
|
const userStreams = state[userId]
|
||||||
|
if (!userStreams) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
const index = userStreams.streams.map(s => s.stream).indexOf(stream)
|
||||||
|
if (index < 0) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
stream.removeTrack(track)
|
||||||
|
if (stream.getTracks().length === 0) {
|
||||||
|
return removeStream(state, {userId, stream})
|
||||||
|
}
|
||||||
|
// UI does not update when a stream track is removed so there is no need to
|
||||||
|
// update the state object
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
|
||||||
export default function streams(
|
export default function streams(
|
||||||
state = defaultState,
|
state = defaultState,
|
||||||
action: StreamAction | MediaStreamAction,
|
action: StreamAction | MediaStreamAction,
|
||||||
@ -108,6 +129,8 @@ export default function streams(
|
|||||||
return addStream(state, action.payload)
|
return addStream(state, action.payload)
|
||||||
case STREAM_REMOVE:
|
case STREAM_REMOVE:
|
||||||
return removeStream(state, action.payload)
|
return removeStream(state, action.payload)
|
||||||
|
case STREAM_TRACK_REMOVE:
|
||||||
|
return removeStreamTrack(state, action.payload)
|
||||||
case MEDIA_STREAM:
|
case MEDIA_STREAM:
|
||||||
if (action.status === 'resolved') {
|
if (action.status === 'resolved') {
|
||||||
return addStream(state, action.payload)
|
return addStream(state, action.payload)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user