Remove each user stream individually

This commit is contained in:
Jerko Steiner 2020-03-10 11:58:15 +01:00
parent f056048d62
commit 46a0b1f7ea
8 changed files with 106 additions and 20 deletions

View File

@ -68,9 +68,20 @@ class PeerHandler {
}
handleTrack = (track: MediaStreamTrack, stream: MediaStream) => {
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({
userId: user.id,
userId,
stream,
}))
}
@ -97,10 +108,13 @@ class PeerHandler {
}
}
handleClose = () => {
const { dispatch, user } = this
debug('peer: %s, close', user.id)
const { dispatch, user, getState } = this
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))
}
}

View File

@ -20,12 +20,16 @@ export interface RemoveStreamAction {
export interface RemoveStreamPayload {
userId: string
stream?: MediaStream
stream: MediaStream
}
export interface SetActiveStreamPayload {
userId: string
}
export interface SetActiveStreamAction {
type: 'ACTIVE_SET'
payload: RemoveStreamPayload
payload: SetActiveStreamPayload
}
export interface ToggleActiveStreamAction {
@ -33,6 +37,17 @@ export interface ToggleActiveStreamAction {
payload: UserIdPayload
}
export interface RemoveStreamTrackPayload {
userId: string
stream: MediaStream
track: MediaStreamTrack
}
export interface RemoveStreamTrackAction {
type: 'PEER_STREAM_TRACK_REMOVE'
payload: RemoveStreamTrackPayload
}
export interface UserIdPayload {
userId: string
}
@ -44,12 +59,19 @@ export const addStream = (payload: AddStreamPayload): AddStreamAction => ({
export const removeStream = (
userId: string,
stream?: MediaStream,
stream: MediaStream,
): RemoveStreamAction => ({
type: constants.STREAM_REMOVE,
payload: { userId, stream },
})
export const removeTrack = (
payload: RemoveStreamTrackPayload,
): RemoveStreamTrackAction => ({
type: constants.STREAM_TRACK_REMOVE,
payload,
})
export const setActive = (userId: string): SetActiveStreamAction => ({
type: constants.ACTIVE_SET,
payload: { userId },
@ -64,4 +86,5 @@ export type StreamAction =
AddStreamAction |
RemoveStreamAction |
SetActiveStreamAction |
ToggleActiveStreamAction
ToggleActiveStreamAction |
RemoveStreamTrackAction

View File

@ -63,7 +63,16 @@ export default class App extends React.PureComponent<AppProps, AppState> {
init()
}
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 () {
const {
@ -86,10 +95,7 @@ export default class App extends React.PureComponent<AppProps, AppState> {
'chat-visible': this.state.chatVisible,
})
const localStreams = streams[constants.ME] || {
userId: constants.ME,
streams: [],
}
const localStreams = this.getLocalStreams()
return (
<div className="app">

View File

@ -24,7 +24,6 @@ function mapStateToProps(state: State) {
const hidden = !!localStream &&
localStream.streams.filter(s => s.type === STREAM_TYPE_CAMERA).length > 0
const visible = !hidden
console.log('visible', visible)
return {
...state.media,
visible,

View File

@ -37,6 +37,7 @@ export const SOCKET_EVENT_USERS = 'users'
export const STREAM_ADD = 'PEER_STREAM_ADD'
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_DESKTOP = 'desktop'

View File

@ -4,6 +4,7 @@ import Peer from 'simple-peer'
import { PeerAction } from '../actions/PeerActions'
import * as constants from '../constants'
import { MediaStreamAction } from '../actions/MediaActions'
import { RemoveStreamAction } from '../actions/StreamActions'
export type PeersState = Record<string, Peer.Instance>
@ -11,9 +12,26 @@ const defaultState: PeersState = {}
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(
state = defaultState,
action: PeerAction | MediaStreamAction,
action: PeerAction | MediaStreamAction | RemoveStreamAction,
): PeersState {
switch (action.type) {
case constants.PEER_ADD:
@ -27,6 +45,8 @@ export default function peers(
localStreams = {}
forEach(state, peer => peer.destroy())
return defaultState
case constants.STREAM_REMOVE:
return handleRemoveStream(state, action)
case constants.MEDIA_STREAM:
if (action.status === 'resolved') {
forEach(state, peer => {

View File

@ -57,11 +57,11 @@ describe('reducers/alerts', () => {
describe('removeStream', () => {
it('removes a stream', () => {
store.dispatch(StreamActions.addStream({ userId, stream }))
store.dispatch(StreamActions.removeStream(userId))
store.dispatch(StreamActions.removeStream(userId, stream))
expect(store.getState().streams).toEqual({})
})
it('does not fail when no stream', () => {
store.dispatch(StreamActions.removeStream(userId))
store.dispatch(StreamActions.removeStream(userId, stream))
})
})

View File

@ -1,7 +1,7 @@
import _debug from 'debug'
import omit from 'lodash/omit'
import { AddStreamAction, RemoveStreamAction, StreamAction, StreamType } from '../actions/StreamActions'
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM } from '../constants'
import { AddStreamAction, RemoveStreamAction, StreamAction, StreamType, RemoveStreamTrackAction } from '../actions/StreamActions'
import { STREAM_ADD, STREAM_REMOVE, MEDIA_STREAM, STREAM_TRACK_REMOVE } from '../constants'
import { createObjectURL, revokeObjectURL } from '../window'
import { MediaStreamAction } from '../actions/MediaActions'
@ -99,6 +99,27 @@ function removeStream (
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(
state = defaultState,
action: StreamAction | MediaStreamAction,
@ -108,6 +129,8 @@ export default function streams(
return addStream(state, action.payload)
case STREAM_REMOVE:
return removeStream(state, action.payload)
case STREAM_TRACK_REMOVE:
return removeStreamTrack(state, action.payload)
case MEDIA_STREAM:
if (action.status === 'resolved') {
return addStream(state, action.payload)