Skip to content

Instantly share code, notes, and snippets.

@anderjs
Created July 20, 2020 20:01
Show Gist options
  • Select an option

  • Save anderjs/e8d62ab599d235f9c1302088a8c04748 to your computer and use it in GitHub Desktop.

Select an option

Save anderjs/e8d62ab599d235f9c1302088a8c04748 to your computer and use it in GitHub Desktop.
import React, { memo, useState, useEffect, useRef, useCallback } from 'react'
import { Badge as Button, Container } from 'react-bootstrap'
import { Icon } from 'react-icons-kit'
import { ic_volume_up } from 'react-icons-kit/md/ic_volume_up'
import { ic_volume_off } from 'react-icons-kit/md/ic_volume_off'
import { ic_videocam_off } from 'react-icons-kit/md/ic_videocam_off'
import { ic_videocam } from 'react-icons-kit/md/ic_videocam'
import { ic_fullscreen } from 'react-icons-kit/md/ic_fullscreen'
import { ic_screen_share } from 'react-icons-kit/md/ic_screen_share'
import 'assets/css/video.css'
import * as TWILIO_EVENT from './actions/twilio'
import useAuthProvider from 'hooks/useAuthProvider'
import useToggler from 'hooks/useToggler'
import Avatar from 'components/Avatar'
import FlexContainer from 'components/FlexContainer'
import Text from 'components/Text'
const defaultBadgeProps = {
variant: 'muted',
className: 'mr-1 ml-1 hovered',
pill: true,
size: 'sm'
}
/**
* @type {React.FunctionComponent<{ participant?: import ('twilio-video').Participant, onShareScreen: () => {} }>}
*/
const Participant = ({ participant, onShareScreen }) => {
const user = useAuthProvider()
const [cameraState, setCameraState] = useToggler(true)
const [microphoneState, setMicrophoneState] = useToggler(true)
const [videoStreaming, setVideoStreaming] = useState([])
const [audioStreaming, setAudioStreaming] = useState([])
const videoRef = useRef()
const audioRef = useRef()
const trackpubsToTracks = trackMap => {
return Array.from(trackMap.values())
.map(publication => publication.track)
.filter(track => track !== null)
}
useEffect(() => {
const trackSubscribed = track => {
if (track.kind === 'video') {
setVideoStreaming(videos => [...videos, track])
} else if (track.kind === 'audio') {
setAudioStreaming(audios => [...audios, track])
}
}
const trackUnsubscribed = track => {
if (track.kind === 'video') {
setVideoStreaming(videos => videos.filter(video => video !== track))
} else if (track.kind === 'audio') {
setAudioStreaming(audios => audios.filter(audio => audio !== track))
}
}
setVideoStreaming(trackpubsToTracks(participant.videoTracks))
setAudioStreaming(trackpubsToTracks(participant.audioTracks))
/**
* @see https://media.twiliocdn.com/sdk/js/video/releases/1.10.0/docs/Room.html#event:trackSubscribed
*/
participant.on(TWILIO_EVENT.TRACK_SUBSCRIBED, trackSubscribed)
/**
* @see https://media.twiliocdn.com/sdk/js/video/releases/1.10.0/docs/Room.html#event:trackUnsubscribed
*/
participant.on(TWILIO_EVENT.TRACK_UNSUBSCRIBED, trackUnsubscribed)
return () => {
setVideoStreaming([])
setAudioStreaming([])
participant.removeAllListeners()
}
}, [participant])
useEffect(() => {
const [video] = videoStreaming
if (video) {
video.attach(videoRef.current)
return () => {
video.detach()
}
}
}, [videoStreaming])
useEffect(() => {
const [audio] = audioStreaming
if (audio) {
audio.attach(audioRef.current)
return () => {
audio.detach()
}
}
}, [audioStreaming])
const isSafeModeCredential = participant.identity === user.profile.email
/**
* @see https://developer.mozilla.org/en-US/docs/Web/Guide/Audio_and_video_delivery/cross_browser_video_player#Fullscreen
* @returns {boolean}
*/
const isFullScreen = () => {
return !!(
document.fullscreen ||
document.webkitIsFullScreen ||
document.mozFullScreen ||
document.msFullscreenElement ||
document.fullscreenElement
)
}
const handleFullScreenState = useCallback(state => {
/**
* @type {HTMLVideoElement}
*/
const ref = videoRef.current
ref.setAttribute('data-fullscreen', state)
}, [])
const handleFullScreen = function () {
const [videoRefNode] = document.getElementsByTagName('video')
if (isFullScreen()) {
const exitFullScreenBrowserOption = [
document.exitFullscreen,
document.mozCancelFullScreen,
document.webkitCancelFullScreen,
document.msExitFullscreen
]
try {
/**
* @description
* Checking that the browser supports full screeen.
*/
exitFullScreenBrowserOption.find(Boolean)()
handleFullScreenState(false)
} catch (err) {
console.error(err)
} finally {
return
}
}
/**
* @type {HTMLVideoElement}
*/
const fullScreenBrowserOption = [
{
key: 'requestFullScreen',
target: () =>
videoRefNode.requestFullscreen({
navigationUI: true
})
},
{
key: 'mozRequestFullScreen',
target: () => videoRefNode.mozRequestFullScreen()
},
{
key: 'webkitRequestFullScreen',
target: () => videoRefNode.webkitRequestFullScreen()
},
{
key: 'msRequestFullscreen',
target: () => videoRefNode.msRequestFullscreen()
}
]
try {
fullScreenBrowserOption.forEach(option => {
if (option.key in videoRefNode) {
option.target()
}
})
} catch (err) {
console.error(err)
}
}
/**
* @description
* Turn off/on camera.
*/
const switchCameraState = useCallback(() => {
const [video] = videoStreaming
if (video) {
setCameraState()
if (cameraState) {
return video.disable()
}
return video.enable()
}
}, [videoStreaming, cameraState, setCameraState])
const switchMicrophoneState = useCallback(() => {
const [audio] = audioStreaming
if (audio) {
setMicrophoneState()
if (microphoneState) {
return audio.disable()
}
return audio.enable()
}
}, [audioStreaming, microphoneState, setMicrophoneState])
const switchScreenShareState = useCallback(() => {
onShareScreen()
}, [onShareScreen])
return (
<div className="participant">
<video
id="video"
width={500}
className="card card-body border-muted bg-darkblue material-shadow bg-light rounded"
autoPlay
ref={videoRef}
/>
<Text className="context-container" color="muted" tag="p">
{isSafeModeCredential && user.profile.firstName}{' '}
{isSafeModeCredential && <Avatar src={user.profile.imageUrl} />}
</Text>
<br />
<Container>
<FlexContainer justify="around">
<Button {...defaultBadgeProps} onClick={switchMicrophoneState}>
<Icon
className="text-muted mr-1"
icon={microphoneState ? ic_volume_off : ic_volume_up}
/>
<Text color="muted" tag="small">
{microphoneState ? 'Silenciar' : 'Activar'}
</Text>
</Button>
<Button {...defaultBadgeProps} onClick={switchCameraState}>
<Icon
className="text-muted mr-1"
icon={cameraState ? ic_videocam_off : ic_videocam}
/>
<Text color="muted" tag="small">
{cameraState ? 'Apagar' : 'Encender'}
</Text>
</Button>
<Button {...defaultBadgeProps} onClick={handleFullScreen}>
<Icon className="text-muted mr-1" icon={ic_fullscreen} />
<Text color="muted" tag="small">
Fullscreen
</Text>
</Button>
<Button {...defaultBadgeProps} onClick={switchScreenShareState}>
<Icon className="text-muted mr-1" icon={ic_screen_share} />
<Text color="muted" tag="small">
Compartir
</Text>
</Button>
</FlexContainer>
</Container>
<audio ref={audioRef} autoPlay />
</div>
)
}
export default memo(Participant)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment