Skip to content

Instantly share code, notes, and snippets.

@AdamZaczek
Last active February 9, 2021 11:02
Show Gist options
  • Save AdamZaczek/acccddc7158351e4f8abcad2d394fe03 to your computer and use it in GitHub Desktop.
Save AdamZaczek/acccddc7158351e4f8abcad2d394fe03 to your computer and use it in GitHub Desktop.
It works I guess
import React, { useEffect, useRef, PureComponent } from 'react'
import AgoraRTC from 'agora-rtc-sdk'
import styled from 'styled-components/macro'
import { VideoCameraOutlined } from '@ant-design/icons'
import { AudioOutlined } from '@ant-design/icons'
import { PhoneOutlined } from '@ant-design/icons'
import SmallSpinner from 'components/smallSpinner'
import CircularProgress from '@material-ui/core/CircularProgress'
const StyledVideo = styled.video`
border-radius: 5px;
width: 150px;
border: 1px solid ${({ theme }) => theme.colors.c1};
${({ hidden }) =>
hidden &&
`
visibility: hidden;
`};
`
const NoVideo = styled.div`
border-radius: 5px;
width: 150px;
height: 115px;
border: 1px solid ${({ theme }) => theme.colors.c1};
background: ${({ theme }) => theme.colors.c3};
color: ${({ theme }) => theme.colors.primaryColor};
display: grid;
place-items: center;
`
const IconsWrapper = styled.div`
position: relative;
bottom: 40px;
display: flex;
z-index: 2;
color: ${({ theme }) => theme.colors.c1};
justify-content: space-around;
`
const StyledCameraIcon = styled(VideoCameraOutlined)`
font-size: 30px;
transition: color 0.3s;
cursor: pointer;
color: ${({ theme, active }) =>
active ? theme.colors.c1 : theme.colors.c23};
:hover {
color: ${({ theme }) => theme.colors.c2} !important;
}
`
const StyledAudioIcon = styled(AudioOutlined)`
font-size: 30px;
transition: color 0.3s;
cursor: pointer;
color: ${({ theme, active }) =>
active ? theme.colors.c1 : theme.colors.c23};
:hover {
color: ${({ theme }) => theme.colors.c2} !important;
}
`
const StyledPhoneIcon = styled(PhoneOutlined)`
font-size: 30px;
transition: color 0.3s;
cursor: pointer;
color: ${({ theme, active }) => theme.colors.c23};
:hover {
color: ${({ theme }) => theme.colors.c2} !important;
}
`
const Video = ({ srcObject, ...props }) => {
const refVideo = useRef()
useEffect(() => {
if (!refVideo.current) return
refVideo.current.srcObject = srcObject
}, [srcObject])
return <StyledVideo ref={refVideo} {...props} />
}
const VideoWrapper = styled.div`
display: flex;
flex-direction: row-reverse;
position: relative;
`
const OuterVideoWrapper = styled(VideoWrapper)`
flex-direction: column;
width: 150px;
`
const OuterSpinnerWrapper = styled.div`
border-radius: 5px;
padding: 10px;
background: ${({ theme }) => theme.colors.primaryBackground};
`
const SpinnerWrapper = styled.div`
border-radius: 5px;
min-width: 50px;
min-height: 50px;
padding: 10px;
position: relative;
background: ${({ theme }) => theme.colors.primaryBackground};
`
const StyledInfo = styled.div`
color: ${({ theme }) => theme.colors.secondaryColor};
background: ${({ theme }) => theme.colors.primaryBackground};
font-size: 16px;
padding: 5px;
text-overflow: ellipsis;
border-radius: 5px;
flex: 0 0 auto;
width: 150px;
height: 115px;
font-weight: 500;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
`
const VideoItemWrapper = styled.div`
width: 150px;
flex: 0 0 auto;
`
class VideoContainer extends PureComponent {
constructor(props) {
super(props)
this.client = {}
this.localStream = {}
this.state = {
displayMode: 'pip',
streamList: [],
readyState: false,
isVideoOn: true,
isAudioOn: true,
}
}
componentWillMount() {
const {
appId,
channel,
uid,
attendeeMode,
videoProfile,
transcode,
userName,
} = this.props
// init AgoraRTC local client
this.client = AgoraRTC.createClient({ mode: transcode })
this.client.init(appId, () => {
/* For some reason those console logs are not visible in devtools but the logic is needed. */
this.subscribeStreamEvents()
this.client.join(appId, channel, uid, async uid => {
// console.log('User ' + uid + ' join channel successfully')
// console.log('At ' + new Date().toLocaleTimeString())
// create local stream
// It is not recommended to setState in function addStream
this.localStream = this.streamInit(uid, attendeeMode, videoProfile, {
userName,
video: await this.detectWebcam(),
})
this.localStream.init(
() => {
if (attendeeMode !== 'audience') {
this.addStream(this.localStream, true)
this.client.publish(this.localStream, err => {
console.log('Publish local stream error: ' + err)
})
}
this.setState({ readyState: true })
},
err => {
console.log('getUserMedia failed', err)
this.setState({ readyState: true })
}
)
})
})
}
detectWebcam = async () => {
let md = navigator.mediaDevices
if (!md || !md.enumerateDevices) return false
const devices = await md.enumerateDevices()
return devices.some(device => 'videoinput' === device.kind)
}
componentWillUnmount() {
this.client && this.client.unpublish(this.localStream)
this.localStream && this.localStream.close()
this.client &&
this.client.leave(
() => {
console.log('Client succeed to leave.')
},
() => {
console.log('Client failed to leave.')
}
)
}
streamInit = (uid, attendeeMode, videoProfile, config) => {
let defaultConfig = {
streamID: uid,
audio: true,
video: true,
screen: false,
AEC: true,
...config,
}
switch (attendeeMode) {
case 'audio-only':
defaultConfig.video = false
break
case 'audience':
defaultConfig.video = false
defaultConfig.audio = false
break
default:
case 'video':
break
}
let stream = AgoraRTC.createStream({ ...defaultConfig, ...config })
stream.setVideoProfile(videoProfile)
return stream
}
subscribeStreamEvents = () => {
let rt = this
rt.client.on('stream-added', function(evt) {
let stream = evt.stream
// console.log('New stream added: ' + stream.getId())
// console.log('At ' + new Date().toLocaleTimeString())
// console.log('Subscribe ', stream)
rt.client.subscribe(stream, function(err) {
console.log('Subscribe stream failed', err)
})
})
rt.client.on('peer-leave', function(evt) {
// console.log('Peer has left: ' + evt.uid)
// console.log(new Date().toLocaleTimeString())
// console.log(evt)
rt.removeStream(evt.uid)
})
rt.client.on('stream-subscribed', function(evt) {
let stream = evt.stream
// console.log('Got stream-subscribed event')
// console.log(new Date().toLocaleTimeString())
// console.log('Subscribe remote stream successfully: ' + stream.getId())
// console.log(evt)
rt.addStream(stream)
})
rt.client.on('stream-removed', function(evt) {
let stream = evt.stream
// console.log('Stream removed: ' + stream.getId())
// console.log(new Date().toLocaleTimeString())
// console.log(evt)
rt.removeStream(stream.getId())
})
}
removeStream = uid => {
const { streamList } = this.state
streamList.forEach((item, index) => {
if (item.getId() === uid) {
item.close()
let element = document.querySelector('#ag-item-' + uid)
if (element) {
element.parentNode.removeChild(element)
}
let tempList = [...streamList]
tempList.splice(index, 1)
this.setState({
streamList: tempList,
})
}
})
}
addStream = (stream, push = false) => {
const { streamList } = this.state
let repetition = streamList.some(item => {
return item.getId() === stream.getId()
})
if (repetition) {
return
}
if (push) {
this.setState({
streamList: this.state.streamList.concat([stream]),
})
} else {
this.setState({
streamList: [stream].concat(this.state.streamList),
})
}
}
handleCamera = e => {
e.currentTarget.classList.toggle('off')
this.localStream.isVideoOn()
? this.localStream.disableVideo()
: this.localStream.enableVideo()
this.setState({ isVideoOn: this.localStream.isVideoOn() })
}
handleMic = e => {
e.currentTarget.classList.toggle('off')
this.localStream.isAudioOn()
? this.localStream.disableAudio()
: this.localStream.enableAudio()
this.setState({ isAudioOn: this.localStream.isAudioOn() })
}
handleExit = e => {
if (e.currentTarget.classList.contains('disabled')) {
return
}
try {
this.client && this.client.unpublish(this.localStream)
this.localStream && this.localStream.close()
this.client &&
this.client.leave(
() => {
console.log('Client succeed to leave.')
},
() => {
console.log('Client failed to leave.')
}
)
} finally {
this.setState({ readyState: false })
this.client = null
this.localStream = null
}
}
render() {
const { streamList, readyState, isAudioOn, isVideoOn } = this.state
const { uid, setVideoOn } = this.props
if (!streamList?.length) {
return (
<StyledInfo>
Waiting for others to toggle video
<CircularProgress size="30px" thickness={5} />
</StyledInfo>
)
}
return (
<OuterVideoWrapper>
<VideoWrapper id="videoCall">
{streamList?.length === 1 && (
<StyledInfo>
Waiting for others to toggle video
<CircularProgress size="30px" thickness={5} />
</StyledInfo>
)}
{readyState ? (
<>
{streamList.map(stream => (
<VideoItemWrapper>
<Video
muted={stream?.params?.streamID === uid}
id={stream?.stream?.id}
autoPlay
srcObject={stream?.stream}
hidden={!stream?.video || !stream?.audio}
/>
{!stream?.video ? (
<NoVideo>No video</NoVideo>
) : (
!stream?.audio && <NoVideo>No audio</NoVideo>
)}
{stream?.params?.streamID === uid && (
<IconsWrapper>
{stream?.audio && (
<StyledAudioIcon
onClick={this.handleMic}
active={isAudioOn}
/>
)}
<StyledPhoneIcon onClick={() => setVideoOn(false)} />
{stream?.video && (
<StyledCameraIcon
onClick={this.handleCamera}
active={isVideoOn}
/>
)}
</IconsWrapper>
)}
</VideoItemWrapper>
))}
</>
) : (
<OuterSpinnerWrapper>
<SpinnerWrapper>
<SmallSpinner />
</SpinnerWrapper>
</OuterSpinnerWrapper>
)}
</VideoWrapper>
</OuterVideoWrapper>
)
}
}
export default VideoContainer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment