"use strict"; (self["webpackChunktinode_webapp"] = self["webpackChunktinode_webapp"] || []).push([["src_widgets_call-panel_jsx"],{ /***/ "./src/widgets/call-panel.jsx": /*!************************************!*\ !*** ./src/widgets/call-panel.jsx ***! \************************************/ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { __webpack_require__.r(__webpack_exports__); /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "react"); /* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-intl */ "react-intl"); /* harmony import */ var react_intl__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_intl__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var _letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./letter-tile.jsx */ "./src/widgets/letter-tile.jsx"); /* harmony import */ var _config_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../config.js */ "./src/config.js"); /* harmony import */ var _constants_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../constants.js */ "./src/constants.js"); /* harmony import */ var _lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../lib/strformat.js */ "./src/lib/strformat.js"); /* harmony import */ var react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! react/jsx-dev-runtime */ "./node_modules/react/jsx-dev-runtime.js"); const RING_SOUND = new Audio('audio/call-out.m4a'); RING_SOUND.loop = true; const CALL_ENDED_SOUND = new Audio('audio/call-end.m4a'); CALL_ENDED_SOUND.loop = true; const DIALING_SOUND = new Audio('audio/dialing.m4a'); const VIDEO_MUTED_EVENT = 'video:muted'; const VIDEO_UNMUTED_EVENT = 'video:unmuted'; const UNMOUNTED_DURING_OFFER = Symbol('unmounted-during-offer'); function formatElapsedTime(totalSeconds) { const h = Math.floor(totalSeconds / 3600); const m = Math.floor(totalSeconds % 3600 / 60); const s = totalSeconds % 60; const pad = n => String(n).padStart(2, '0'); return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`; } const messages = (0,react_intl__WEBPACK_IMPORTED_MODULE_1__.defineMessages)({ already_in_call: { id: 'already_in_call', defaultMessage: 'You already in an ongoing call!', description: 'Error message when the user tried to accept a new call without finishing pervious one' } }); class CallPanel extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComponent) { constructor(props) { super(props); this.state = { localStream: undefined, remoteStream: undefined, pc: undefined, dataChannel: undefined, previousOnInfo: undefined, waitingForPeer: false, callInitialSetupComplete: false, audioOnly: props.callAudioOnly, videoToggleInProgress: false, remoteVideoLive: false, elapsedSeconds: 0, speakerOn: true }; this.callStartedAt = null; this.elapsedTimerId = null; this.localStreamConstraints = { audio: true, video: !props.callAudioOnly }; this.isOutgoingCall = props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED; this.containerRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef(); this.localRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef(); this.remoteRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef(); this.remoteIceCandidatesCache = []; this.onInfo = this.onInfo.bind(this); this.start = this.start.bind(this); this.stop = this.stop.bind(this); this.createPeerConnection = this.createPeerConnection.bind(this); this.canSendOffer = this.canSendOffer.bind(this); this.drainRemoteIceCandidatesCache = this.drainRemoteIceCandidatesCache.bind(this); this.handleNegotiationNeededEvent = this.handleNegotiationNeededEvent.bind(this); this.handleICECandidateEvent = this.handleICECandidateEvent.bind(this); this.handleNewICECandidateMsg = this.handleNewICECandidateMsg.bind(this); this.handleICEConnectionStateChangeEvent = this.handleICEConnectionStateChangeEvent.bind(this); this.handleSignalingStateChangeEvent = this.handleSignalingStateChangeEvent.bind(this); this.handleICEGatheringStateChangeEvent = this.handleICEGatheringStateChangeEvent.bind(this); this.handleIceCandidateErrorEvent = this.handleIceCandidateErrorEvent.bind(this); this.handleTrackEvent = this.handleTrackEvent.bind(this); this.handleVideoOfferMsg = this.handleVideoOfferMsg.bind(this); this.handleVideoAnswerMsg = this.handleVideoAnswerMsg.bind(this); this.handleNewICECandidateMsg = this.handleNewICECandidateMsg.bind(this); this.reportError = this.reportError.bind(this); this.handleGetUserMediaError = this.handleGetUserMediaError.bind(this); this.stopTracks = this.stopTracks.bind(this); this.disconnectMedia = this.disconnectMedia.bind(this); this.handleCloseClick = this.handleCloseClick.bind(this); this.handleToggleCameraClick = this.handleToggleCameraClick.bind(this); this.handleToggleMicClick = this.handleToggleMicClick.bind(this); this.handleToggleSpeakerClick = this.handleToggleSpeakerClick.bind(this); this.handleSwitchCameraClick = this.handleSwitchCameraClick.bind(this); this.startElapsedTimer = this.startElapsedTimer.bind(this); this.stopElapsedTimer = this.stopElapsedTimer.bind(this); this.handleRemoteHangup = this.handleRemoteHangup.bind(this); this.handleVideoCallAccepted = this.handleVideoCallAccepted.bind(this); this.muteVideo = this.muteVideo.bind(this); this.unmuteVideo = this.unmuteVideo.bind(this); this.emptyVideoTrack = this.emptyVideoTrack.bind(this); this.handleDataChannelEvent = this.handleDataChannelEvent.bind(this); this.handleDataChannelError = this.handleDataChannelError.bind(this); this.handleDataChannelMessage = this.handleDataChannelMessage.bind(this); this.handleDataChannelOpen = this.handleDataChannelOpen.bind(this); this.handleDataChannelClose = this.handleDataChannelClose.bind(this); this.bounds = {}; this.dragStart = event => { if (!event.target.closest('.draggable')) { return; } if (window.matchMedia('(width<=640px)').matches) { return; } if (this.props.minimized && this.containerRef) { event.stopPropagation(); const el = this.containerRef.current; el.setPointerCapture(event.pointerId); el.style.cursor = 'grabbing'; this.bounds.left = el.offsetLeft; this.bounds.top = el.offsetTop; this.bounds.width = el.offsetWidth; this.bounds.height = el.offsetHeight; el.style.right = 'auto'; el.style.left = `${this.bounds.left}px`; el.style.top = `${this.bounds.top}px`; const parent = el.offsetParent; this.bounds.maxX = parent.offsetWidth; this.bounds.maxY = parent.offsetHeight; } }; this.drag = event => { event.stopPropagation(); const el = this.containerRef.current; if (this.props.minimized && this.containerRef && el.hasPointerCapture(event.pointerId)) { this.bounds.left = Math.min(Math.max(this.bounds.left + event.movementX, 0), this.bounds.maxX - this.bounds.width); this.bounds.top = Math.min(Math.max(this.bounds.top + event.movementY, 0), this.bounds.maxY - this.bounds.height); el.style.left = `${this.bounds.left}px`; el.style.top = `${this.bounds.top}px`; } }; this.dragEnd = event => { event.stopPropagation(); if (this.props.minimized && this.containerRef) { this.containerRef.current.releasePointerCapture(event.pointerId); this.containerRef.current.style.cursor = 'grab'; } }; } componentDidMount() { const topic = this.props.tinode.getTopic(this.props.topic); this.previousOnInfo = topic.onInfo; topic.onInfo = this.onInfo; if ((this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_OUTGOING_INITATED || this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) && this.localRef.current) { this.start(); } if (this.containerRef) { this.containerRef.current.addEventListener('pointerdown', this.dragStart); this.containerRef.current.addEventListener('pointermove', this.drag); this.containerRef.current.addEventListener('pointerup', this.dragEnd); } } componentWillUnmount() { this._unmounted = true; const topic = this.props.tinode.getTopic(this.props.topic); topic.onInfo = this.previousOnInfo; this.stop(); if (this.containerRef) { this.containerRef.current.removeEventListener('pointerdown', this.dragStart); this.containerRef.current.removeEventListener('pointermove', this.drag); this.containerRef.current.removeEventListener('pointerup', this.dragEnd); } } handleVideoCallAccepted(info) { RING_SOUND.pause(); const pc = this.createPeerConnection(true); const stream = this.state.localStream; stream.getTracks().forEach(track => { pc.addTrack(track, stream); if (track.kind == 'video' && this.state.audioOnly) { track.enabled = false; track.stop(); stream.removeTrack(track); } }); } onInfo(info) { if (info.what != 'call') { if (this.previousOnInfo) { this.previousOnInfo(info); } return; } switch (info.event) { case 'accept': this.handleVideoCallAccepted(info); break; case 'answer': this.handleVideoAnswerMsg(info); break; case 'ice-candidate': this.handleNewICECandidateMsg(info); break; case 'hang-up': this.handleRemoteHangup(info); break; case 'offer': this.handleVideoOfferMsg(info); break; case 'ringing': RING_SOUND.play().catch(_ => {}); break; default: console.warn("Unknown call event", info.event); break; } } emptyVideoTrack() { const width = 640; const height = 480; const canvas = Object.assign(document.createElement("canvas"), { width, height }); canvas.getContext('2d').fillRect(0, 0, width, height); const stream = canvas.captureStream(0); return Object.assign(stream.getVideoTracks()[0], { enabled: false }); } start() { if (this.state.localStream) { this.props.onError(this.props.intl.formatMessage(messages.already_in_call), 'info'); return; } if (this.props.callState == _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS) { this.props.onInvite(this.props.topic, this.props.seq, _constants_js__WEBPACK_IMPORTED_MODULE_4__.CALL_STATE_IN_PROGRESS, this.props.callAudioOnly); return; } navigator.mediaDevices.getUserMedia(this.localStreamConstraints).then(stream => { if (this._unmounted) { stream.getTracks().forEach(t => t.stop()); return; } if (!this.localStreamConstraints.video) { stream.addTrack(this.emptyVideoTrack()); } this.setState({ localStream: stream, waitingForPeer: true }); this.localRef.current.srcObject = stream; DIALING_SOUND.play(); this.props.onInvite(this.props.topic, this.props.seq, this.props.callState, this.props.callAudioOnly); }).catch(this.handleGetUserMediaError); } stop() { CALL_ENDED_SOUND.pause(); CALL_ENDED_SOUND.currentTime = 0; RING_SOUND.pause(); RING_SOUND.currentTime = 0; this.stopElapsedTimer(); this.stopTracks(this.state.localStream); this.stopTracks(this.state.remoteStream); this.disconnectMedia(this.localRef.current); this.disconnectMedia(this.remoteRef.current); if (this.state.pc) { this.state.pc.ontrack = null; this.state.pc.onremovetrack = null; this.state.pc.onremovestream = null; this.state.pc.onicecandidate = null; this.state.pc.oniceconnectionstatechange = null; this.state.pc.onsignalingstatechange = null; this.state.pc.onicegatheringstatechange = null; this.state.pc.onnegotiationneeded = null; this.state.pc.onicecandidateerror = null; this.state.pc.ondatachannel = null; if (this.state.dataChannel && (this.state.dataChannel.readyState == 'open' || this.state.dataChannel.readyState == 'connecting')) { this.state.dataChannel.close(); } this.state.pc.close(); } this.setState({ pc: null, waitingForPeer: false }); } disconnectMedia(el) { if (!el) { return; } el.srcObject = null; el.src = ''; } stopTracks(stream) { if (!stream) { return; } let tracks = stream.getTracks(); if (tracks) { tracks.forEach(track => { track.stop(); track.enabled = false; }); } } handleDataChannelError(error) { console.error('data channel error', error); } handleDataChannelMessage(event) { switch (event.data) { case VIDEO_MUTED_EVENT: this.setState({ remoteVideoLive: false }, _ => { this.remoteRef.current.srcObject = this.state.remoteStream; }); break; case VIDEO_UNMUTED_EVENT: this.setState({ remoteVideoLive: true }, _ => { this.remoteRef.current.srcObject = this.state.remoteStream; }); break; default: break; } } handleDataChannelOpen(event) { if (!this.state.audioOnly) { event.target.send(VIDEO_UNMUTED_EVENT); } } handleDataChannelClose(event) { console.info('close data channel:', event); } handleDataChannelEvent(event) { console.info('data channel event:', event); const channel = event.channel; channel.onerror = this.handleDataChannelError; channel.onmessage = this.handleDataChannelMessage; channel.onopen = this.handleDataChannelOpen; channel.onclose = this.handleDataChannelClose; this.setState({ dataChannel: channel }); } createPeerConnection(withDataChannel) { const iceServers = this.props.tinode.getServerParam('iceServers', null); const pc = iceServers ? new RTCPeerConnection({ iceServers: iceServers }) : new RTCPeerConnection(); pc.onicecandidate = this.handleICECandidateEvent; pc.oniceconnectionstatechange = this.handleICEConnectionStateChangeEvent; pc.onicegatheringstatechange = this.handleICEGatheringStateChangeEvent; pc.onsignalingstatechange = this.handleSignalingStateChangeEvent; pc.onnegotiationneeded = this.handleNegotiationNeededEvent; pc.onicecandidateerror = this.handleIceCandidateErrorEvent; pc.ontrack = this.handleTrackEvent; pc.ondatachannel = this.handleDataChannelEvent; let stateUpdate = { pc: pc, waitingForPeer: false }; if (withDataChannel) { const channel = pc.createDataChannel("events", { ordered: true }); channel.onerror = this.handleDataChannelError; channel.onmessage = this.handleDataChannelMessage; channel.onopen = this.handleDataChannelOpen; channel.onclose = this.handleDataChannelClose; stateUpdate.dataChannel = channel; } this.setState(stateUpdate); return pc; } handleVideoAnswerMsg(info) { const desc = new RTCSessionDescription(info.payload); this.state.pc.setRemoteDescription(desc).then(_ => { this.setState({ callInitialSetupComplete: true }, _ => this.drainRemoteIceCandidatesCache()); }).catch(err => this.reportError(err)); } reportError(err) { this.props.onError(err.message, 'err'); } canSendOffer() { return this.isOutgoingCall || this.state.callInitialSetupComplete; } handleNegotiationNeededEvent(event) { const pc = event.target; if (!this.canSendOffer()) { return; } pc.createOffer().then(offer => { return pc.setLocalDescription(offer); }).then(_ => { this.props.onSendOffer(this.props.topic, this.props.seq, pc.localDescription.toJSON()); }).catch(err => this.reportError(err)); } handleIceCandidateErrorEvent(event) { console.warn("ICE candidate error:", event); } handleICECandidateEvent(event) { if (event.candidate) { this.props.onIceCandidate(this.props.topic, this.props.seq, event.candidate.toJSON()); } } handleNewICECandidateMsg(info) { const candidate = new RTCIceCandidate(info.payload); if (this.state.callInitialSetupComplete) { this.state.pc.addIceCandidate(candidate).catch(err => { if (candidate.candidate) { this.reportError(err); } console.warn("Error adding new ice candidate", candidate, err); }); } else { this.remoteIceCandidatesCache.push(candidate); } } drainRemoteIceCandidatesCache() { this.remoteIceCandidatesCache.forEach(candidate => { this.state.pc.addIceCandidate(candidate).catch(err => { if (candidate.candidate) { this.reportError(err); } console.warn("Error adding cached ice candidate", candidate, err); }); }); this.remoteIceCandidatesCache = []; } handleICEConnectionStateChangeEvent(event) { switch (event.target.iceConnectionState) { case 'closed': case 'failed': this.handleCloseClick(); break; } } handleSignalingStateChangeEvent(event) { if (event.target.signalingState == 'closed') { this.handleCloseClick(); } } handleICEGatheringStateChangeEvent(event) {} handleTrackEvent(event) { this.remoteRef.current.srcObject = event.streams[0]; this.setState({ remoteStream: event.streams[0] }); this.startElapsedTimer(); } startElapsedTimer() { if (this.elapsedTimerId) { return; } this.callStartedAt = Date.now(); this.elapsedTimerId = setInterval(_ => { this.setState({ elapsedSeconds: Math.floor((Date.now() - this.callStartedAt) / 1000) }); }, 1000); } stopElapsedTimer() { if (this.elapsedTimerId) { clearInterval(this.elapsedTimerId); this.elapsedTimerId = null; } this.callStartedAt = null; } handleGetUserMediaError(e) { console.error("Error opening camera and/or microphone", e); switch (e.name) { case 'NotFoundError': this.reportError(e); break; case 'SecurityError': case 'PermissionDeniedError': break; default: this.reportError(e); break; } this.handleCloseClick(); } handleVideoOfferMsg(info) { let localStream = null; const pc = this.state.pc ? this.state.pc : this.createPeerConnection(false); const desc = new RTCSessionDescription(info.payload); pc.setRemoteDescription(desc).then(_ => { return navigator.mediaDevices.getUserMedia(this.localStreamConstraints); }).then(stream => { if (this._unmounted) { stream.getTracks().forEach(t => t.stop()); return Promise.reject(UNMOUNTED_DURING_OFFER); } let dummyVideo; if (!this.localStreamConstraints.video) { dummyVideo = this.emptyVideoTrack(); stream.addTrack(dummyVideo); } localStream = stream; this.localRef.current.srcObject = stream; this.setState({ localStream: stream }); localStream.getTracks().forEach(track => { pc.addTrack(track, localStream); }); if (dummyVideo) { dummyVideo.enabled = false; dummyVideo.stop(); stream.removeTrack(dummyVideo); } }).then(_ => { return pc.createAnswer(); }).then(answer => { return pc.setLocalDescription(answer); }).then(_ => { this.props.onSendAnswer(this.props.topic, this.props.seq, pc.localDescription.toJSON()); this.setState({ callInitialSetupComplete: true }, _ => this.drainRemoteIceCandidatesCache()); }).catch(err => { if (err !== UNMOUNTED_DURING_OFFER) { this.handleGetUserMediaError(err); } }); } handleRemoteHangup() { if (!this.state.waitingForPeer) { this.handleCloseClick(); } else { this.setState({ waitingForPeer: false }); RING_SOUND.pause(); RING_SOUND.currentTime = 0; CALL_ENDED_SOUND.loop = true; CALL_ENDED_SOUND.play().catch(_ => {}); setTimeout(_ => { this.handleCloseClick(); }, 2000); } } handleCloseClick() { this.stop(); this.props.onHangup(this.props.topic, this.props.seq); } muteVideo() { const stream = this.state.localStream; const t = stream.getVideoTracks()[0]; if (!t) { this.setState({ videoToggleInProgress: false }); return; } t.enabled = false; t.stop(); stream.removeTrack(t); this.sendOnDataChannel(VIDEO_MUTED_EVENT); this.setState({ videoToggleInProgress: false }); } replaceLocalVideoTrack(track) { this.localRef.current.srcObject = null; this.state.localStream.getVideoTracks().forEach(old => this.state.localStream.removeTrack(old)); this.state.localStream.addTrack(track); let result = Promise.resolve(); if (this.state.pc) { const sender = this.state.pc.getSenders().find(s => s.track && s.track.kind == 'video'); if (sender) { result = sender.replaceTrack(track); } else { this.state.pc.addTrack(track, this.state.localStream); } } return result.then(_ => { this.localRef.current.srcObject = this.state.localStream; }); } unmuteVideo() { navigator.mediaDevices.getUserMedia({ video: true }).then(stream => { const track = stream.getVideoTracks()[0]; stream.removeTrack(track); if (this._unmounted) { track.stop(); return; } return this.replaceLocalVideoTrack(track); }).then(_ => { this.sendOnDataChannel(VIDEO_UNMUTED_EVENT); }).catch(err => this.handleGetUserMediaError(err)).finally(_ => { if (!this._unmounted) { this.setState({ videoToggleInProgress: false }); } }); } sendOnDataChannel(message) { if (this.state.dataChannel && this.state.dataChannel.readyState == 'open') { this.state.dataChannel.send(message); } } handleSwitchCameraClick() { if (this.state.videoToggleInProgress || this.state.audioOnly) { return; } const currentTrack = this.state.localStream.getVideoTracks()[0]; const currentDeviceId = currentTrack && currentTrack.getSettings && currentTrack.getSettings().deviceId; this.setState({ videoToggleInProgress: true }, _ => { navigator.mediaDevices.enumerateDevices().then(devices => { const cameras = devices.filter(d => d.kind == 'videoinput'); if (cameras.length < 2) { return null; } const idx = cameras.findIndex(d => d.deviceId == currentDeviceId); const next = cameras[(idx + 1) % cameras.length]; return navigator.mediaDevices.getUserMedia({ video: { deviceId: { exact: next.deviceId } } }); }).then(stream => { if (!stream) { return null; } const track = stream.getVideoTracks()[0]; stream.removeTrack(track); if (this._unmounted) { track.stop(); return null; } return this.replaceLocalVideoTrack(track); }).catch(err => this.handleGetUserMediaError(err)).finally(_ => { if (!this._unmounted) { this.setState({ videoToggleInProgress: false }); } }); }); } handleToggleCameraClick() { if (this.state.videoToggleInProgress) { return; } const tracks = this.state.localStream.getVideoTracks(); this.setState({ videoToggleInProgress: true }, _ => { if (tracks && tracks.length > 0 && tracks[0].enabled && tracks[0].readyState == 'live') { this.muteVideo(); } else { this.unmuteVideo(); } this.setState({ audioOnly: !this.state.audioOnly }); }); } handleToggleMicClick() { const stream = this.state.localStream; const t = stream.getAudioTracks()[0]; t.enabled = !t.enabled; this.forceUpdate(); } static speakerSwitchingSupported() { return typeof HTMLMediaElement !== 'undefined' && 'setSinkId' in HTMLMediaElement.prototype; } handleToggleSpeakerClick() { if (!CallPanel.speakerSwitchingSupported() || !this.remoteRef.current) { return; } const nextOn = !this.state.speakerOn; navigator.mediaDevices.enumerateDevices().then(devices => { const outputs = devices.filter(d => d.kind == 'audiooutput'); const match = nextOn ? outputs.find(d => /speaker/i.test(d.label)) : outputs.find(d => /earpiece|receiver/i.test(d.label)); if (this._unmounted || !this.remoteRef.current) { return null; } return this.remoteRef.current.setSinkId(match ? match.deviceId : ''); }).then(_ => { if (!this._unmounted) { this.setState({ speakerOn: nextOn }); } }).catch(err => console.error('Failed to switch audio output device', err)); } render() { const audioTracks = this.state.localStream && this.state.localStream.getAudioTracks(); const videoTracks = !this.state.audioOnly && this.state.localStream && this.state.localStream.getVideoTracks(); const disabled = !(audioTracks && audioTracks[0]); const micOn = !!(audioTracks && audioTracks[0] && audioTracks[0].enabled); const audioIcon = micOn ? 'mic' : 'mic_off'; const videoOn = !!(videoTracks && videoTracks[0] && videoTracks[0].enabled && videoTracks[0].readyState == 'live'); const videoIcon = videoOn ? 'videocam' : 'videocam_off'; const speakerSupported = CallPanel.speakerSwitchingSupported(); const speakerIcon = this.state.speakerOn ? 'volume_up' : 'volume_off'; const peerTitle = (0,_lib_strformat_js__WEBPACK_IMPORTED_MODULE_5__.truncateString)(this.props.title, _config_js__WEBPACK_IMPORTED_MODULE_3__.MAX_PEER_TITLE_LENGTH); const pulseAnimation = this.state.waitingForPeer ? ' pulse' : ''; const connected = !!this.state.remoteStream; let remoteActive = false; if (this.remoteRef.current && this.remoteRef.current.srcObject && this.state.remoteVideoLive) { const rstream = this.remoteRef.current.srcObject; if (rstream.getVideoTracks().length > 0) { const t = rstream.getVideoTracks()[0]; remoteActive = t.enabled && t.readyState == 'live'; } } const minimizedClass = this.props.minimized ? 'minimized' : null; const fullScreen = this.props.minimized ? 'fullscreen' : 'fullscreen_exit'; if (this.props.minimized && this.state.audioOnly) { return ((0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { id: "video-container", className: "minimized audio-minimized", ref: this.containerRef, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "call-pill-row", children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "call-pill draggable", children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: "call" }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("span", { className: "call-pill-duration", children: formatElapsedTime(this.state.elapsedSeconds) }, void 0, false)] }, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: "full-screen", onClick: this.props.onToggleMinimize, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: fullScreen }, void 0, false) }, void 0, false)] }, void 0, true) }, void 0, false) ); } return (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { id: "video-container", className: minimizedClass, ref: this.containerRef, children: [connected && (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "call-duration", children: formatElapsedTime(this.state.elapsedSeconds) }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { id: "video-container-panel", className: "draggable", children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "call-party self", disabled: this.state.audioOnly || this.props.minimized, children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("video", { ref: this.localRef, autoPlay: true, muted: true, playsInline: true }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "caller-name inactive", children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(react_intl__WEBPACK_IMPORTED_MODULE_1__.FormattedMessage, { id: "calls_you_label", defaultMessage: "You", description: "Shown over the local video screen" }, void 0, false) }, void 0, false)] }, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "call-party peer", disabled: !remoteActive, children: remoteActive ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.Fragment, { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("video", { ref: this.remoteRef, autoPlay: true, playsInline: true }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "caller-name inactive", children: peerTitle }, void 0, false)] }, void 0, true) : (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.Fragment, { children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("audio", { ref: this.remoteRef, autoPlay: true }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: `caller-card${pulseAnimation} draggable`, children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "avatar-box", children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)(_letter_tile_jsx__WEBPACK_IMPORTED_MODULE_2__["default"], { authorizeURL: this.props.tinode.authorizeURL, avatar: this.props.avatar, topic: this.props.topic, title: this.props.title }, void 0, false) }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { className: "caller-name", children: peerTitle }, void 0, false), !connected ? (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("p", { className: "caller-status", children: "正在等待对方接受邀请…" }, void 0, false) : null] }, void 0, true)] }, void 0, true) }, void 0, false)] }, void 0, true), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: "full-screen", onClick: this.props.onToggleMinimize, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: fullScreen }, void 0, false) }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("div", { id: "controls", className: minimizedClass, children: [(0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: "danger", onClick: this.handleCloseClick, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: "call_end" }, void 0, false) }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: `secondary${videoOn ? '' : ' off'}`, onClick: this.handleToggleCameraClick, disabled: disabled, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: videoIcon }, void 0, false) }, void 0, false), (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: `secondary${micOn ? '' : ' off'}`, onClick: this.handleToggleMicClick, disabled: disabled, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: audioIcon }, void 0, false) }, void 0, false), speakerSupported && (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: `secondary${this.state.speakerOn ? '' : ' off'}`, onClick: this.handleToggleSpeakerClick, disabled: disabled, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: speakerIcon }, void 0, false) }, void 0, false), !this.state.audioOnly && (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("button", { className: "secondary", onClick: this.handleSwitchCameraClick, disabled: disabled || !videoOn, children: (0,react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_6__.jsxDEV)("i", { className: "material-icons", children: "flip_camera_android" }, void 0, false) }, void 0, false)] }, void 0, true)] }, void 0, true); } } ; /* harmony default export */ __webpack_exports__["default"] = ((0,react_intl__WEBPACK_IMPORTED_MODULE_1__.injectIntl)(CallPanel)); /***/ }) }]); //# sourceMappingURL=src_widgets_call-panel_jsx.dev.js.map