Commit 87a58b2c authored by Trần Minh Sơn's avatar Trần Minh Sơn

Merge branch 'dev.hainn3' into 'master'

Add highfive feature

See merge request !1
parents c2cf78d4 943a0688
......@@ -134,6 +134,7 @@ import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/Au
import { createPresenterEffect } from './react/features/stream-effects/presenter';
import { endpointMessageReceived } from './react/features/subtitles';
import UIEvents from './service/UI/UIEvents';
import { highFive } from './react/features/toolbox/actions';
const logger = Logger.getLogger(__filename);
......@@ -2507,6 +2508,15 @@ export default {
APP.UI.onSharedVideoUpdate(id, value, attributes);
}
});
room.addCommandListener('HIGH_FIVE', (data, from) => {
const localParticipant = getLocalParticipant(APP.store.getState());
if(data.value == localParticipant.id){
APP.store.dispatch(highFive(true));
setTimeout(() => { APP.store.dispatch(highFive(false)); }, 4000);
}
});
},
/**
......
......@@ -715,3 +715,20 @@
white-space: nowrap;
width: 100%;
}
.high-five {
position: relative;
animation-name: highfive;
animation-duration: 4s;
animation-iteration-count: 1;
animation-direction: alternate-reverse;
z-index: 99999999;
width: 100px;
}
@keyframes highfive {
0% {left:0px; top:0px; width: 100px;}
25% {left:250px; top:150px; width: 150px;}
50% {left:500px; top:250px; width: 200px;}
75% {left:250px; top:200px; width: 150px;}
100% {left:0px; top:0px; width: 100px;}
}
\ No newline at end of file
......@@ -188,9 +188,9 @@ class Conference extends AbstractConference<Props, *> {
_isLobbyScreenVisible,
_isParticipantsPaneVisible,
_layoutClassName,
_showPrejoin
_showPrejoin,
_showHighFive
} = this.props;
return (
<div id = 'layout_wrapper'>
<div
......@@ -206,7 +206,7 @@ class Conference extends AbstractConference<Props, *> {
{!_isParticipantsPaneVisible && <KnockingParticipantList />}
<Filmstrip />
</div>
{_showHighFive && <img className="high-five" src='./images/highfive-jitsi.png' alt="High Five..." />}
{ _showPrejoin || _isLobbyScreenVisible || <Toolbox /> }
<Chat />
......@@ -312,7 +312,8 @@ function _mapStateToProps(state) {
_isParticipantsPaneVisible: getParticipantsPaneOpen(state),
_layoutClassName: LAYOUT_CLASSNAMES[getCurrentLayout(state)],
_roomName: getConferenceNameForTitle(state),
_showPrejoin: isPrejoinPageVisible(state)
_showPrejoin: isPrejoinPageVisible(state),
_showHighFive: state['features/toolbox'].showHighFive,
};
}
......
......@@ -116,3 +116,11 @@ export const SET_TOOLBOX_VISIBLE = 'SET_TOOLBOX_VISIBLE';
* }
*/
export const TOGGLE_TOOLBOX_VISIBLE = 'TOGGLE_TOOLBOX_VISIBLE';
/**
* The type of the redux action which show high five of two participants.
*
* {
* type: HIGH_FIVE
* }
*/
export const HIGH_FIVE = 'HIGH_FIVE';
......@@ -5,7 +5,8 @@ import type { Dispatch } from 'redux';
import {
FULL_SCREEN_CHANGED,
SET_FULL_SCREEN,
SET_OVERFLOW_DRAWER
SET_OVERFLOW_DRAWER,
HIGH_FIVE
} from './actionTypes';
import {
clearToolboxTimeout,
......@@ -160,3 +161,10 @@ export function setOverflowDrawer(displayAsDrawer: boolean) {
displayAsDrawer
};
}
export function highFive(showHighFive: boolean) {
return {
type: HIGH_FIVE,
showHighFive
};
}
......@@ -13,7 +13,8 @@ import {
SET_TOOLBOX_TIMEOUT,
SET_TOOLBOX_TIMEOUT_MS,
SET_TOOLBOX_VISIBLE,
TOGGLE_TOOLBOX_VISIBLE
TOGGLE_TOOLBOX_VISIBLE,
HIGH_FIVE
} from './actionTypes';
declare var interfaceConfig: Object;
......@@ -30,7 +31,8 @@ declare var interfaceConfig: Object;
* overflowMenuVisible: boolean,
* timeoutID: number,
* timeoutMS: number,
* visible: boolean
* visible: boolean,
* showHighFive: boolean,
* }}
*/
function _getInitialState() {
......@@ -116,7 +118,9 @@ function _getInitialState() {
*
* @type {boolean}
*/
visible
visible,
showHighFive: false
};
}
......@@ -185,6 +189,12 @@ ReducerRegistry.register(
case TOGGLE_TOOLBOX_VISIBLE:
return set(state, 'visible', state.alwaysVisible || !state.visible);
case HIGH_FIVE:
return {
...state,
showHighFive: action.showHighFive
};
}
return state;
......
......@@ -7,10 +7,7 @@ import { connect } from '../../../base/redux';
import { updateSettings } from '../../../base/settings';
import VideoMenuButton from './VideoMenuButton';
import * as handTrack from 'handtrackjs';
import {captureLargeVideoScreenshot} from '../../../large-video';
import { getTrackByMediaTypeAndParticipant } from '../../../base/tracks';
import { MEDIA_TYPE } from '../../../base/media';
/**
* The type of the React {@code Component} props of {@link FlipLocalVideoButton}.
......@@ -65,7 +62,7 @@ class FlipLocalVideoButton extends PureComponent<Props> {
return (
<VideoMenuButton
buttonText = { "Handjs-detect" }
buttonText = { "Flip" }
displayClass = 'fliplink'
id = 'flipLocalVideoButton'
onClick = { this._onClick } />
......@@ -81,53 +78,11 @@ class FlipLocalVideoButton extends PureComponent<Props> {
* @returns {void}
*/
_onClick() {
console.log('hainn----_onClick---flip---1-2-');
// const { _localFlipX, dispatch } = this.props;
// dispatch(updateSettings({
// localFlipX: !_localFlipX
// }));
const largeVideo = ((document.getElementById('largeVideo'): any): HTMLVideoElement);
const localVideo = ((document.getElementById('localVideo_container'): any): HTMLVideoElement);
// var img = this.props.dispatch(captureLargeVideoScreenshot());
// const canvas = document.getElementById("canvas");
// const context = canvas.getContext("2d");
// Load the model.
// const defaultParams = {
// modelType: "ssd320fpnlite"
// };
const modelParams = {
modelType: "ssd320fpnlite",
flipHorizontal: true, // flip e.g for video
maxNumBoxes: 20, // maximum number of boxes to detect
iouThreshold: 0.5, // ioU threshold for non-max suppression
scoreThreshold: 0.6, // confidence threshold for predictions.
}
handTrack.load(modelParams).then(model => {
// detect objects in the image.
console.log("model loaded");
const tracks = state['features/base/tracks'];
const participantTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideo.participantId);
var checkHighFive = setInterval(()=>{
let teacherHand = false;
model.detect(largeVideo).then(predictions => {
console.log('Predictions: ', predictions);
if(predictions.lenght > 0 && predictions[0].label == "open"){
teacherHand = true;
// alert('raise hand');
}
});
model.detect(largeVideo).then(predictions => {
console.log('Predictions: ', predictions);
if(predictions.lenght > 0 && predictions[0].label == "open" && teacherHand){
// alert('high five');
clearInterval(checkHighFive);
}
});
}, 100);
});
const { _localFlipX, dispatch } = this.props;
dispatch(updateSettings({
localFlipX: !_localFlipX
}));
}
}
......
// @flow
import React, { Component } from 'react';
import { translate } from '../../../base/i18n';
import { IconRaisedHandHollow } from '../../../base/icons';
import { connect } from '../../../base/redux';
import {
_mapStateToProps as _abstractMapStateToProps,
type Props as AbstractProps
} from '../../../chat/components/PrivateMessageButton';
import { isButtonEnabled } from '../../../toolbox/functions.web';
import VideoMenuButton from './VideoMenuButton';
import {
getLocalVideoTrack,
getTrackByMediaTypeAndParticipant,
} from '../../../base/tracks';
import { MEDIA_TYPE, VideoTrack } from '../../../base/media';
import { highFive } from '../../../toolbox/actions';
import { getCurrentConference } from '../../../base/conference';
const handpose = require('@tensorflow-models/handpose');
require('@tensorflow/tfjs-backend-webgl');
declare var interfaceConfig: Object;
var checkHighFive;
type Props = AbstractProps & {
/**
* True if the private chat functionality is disabled, hence the button is not visible.
*/
_hidden: boolean
};
/**
* A custom implementation of the PrivateMessageButton specialized for
* the web version of the remote video menu. When the web platform starts to use
* the {@code AbstractButton} component for the remote video menu, we can get rid
* of this component and use the generic button in the chat feature.
*/
class HighFiveButton extends Component<Props> {
/**
* Instantiates a new Component instance.
*
* @inheritdoc
*/
constructor(props: Props) {
super(props);
this._onClick = this._onClick.bind(this);
this.state = {
currentParticipant: ''
}
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
render() {
const { participantID, t } = this.props;
return (
<VideoMenuButton
buttonText = { "High Five" }
icon = { IconRaisedHandHollow }
id = { `privmsglink_${participantID}` }
onClick = { this._onClick } />
);
}
_onClick: () => void;
/**
* Callback to be invoked on pressing the button.
*
* @returns {void}
*/
_onClick = async () => {
const { dispatch, _participant } = this.props;
if(_participant.id == this.state.currentParticipant){
clearInterval(checkHighFive);
this.setState({currentParticipant: ""});
} else {
this.setState({currentParticipant: _participant.id});
const largeVideo = ((document.getElementById('largeVideo'): any): HTMLVideoElement);
const localVideo = ((document.getElementById('localVideo_container'): any): HTMLVideoElement);
const isLocal = _participant?.local ?? true;
const _videoTrack = isLocal
? getLocalVideoTrack(tracks) : getTrackByMediaTypeAndParticipant(this.props.tracks, MEDIA_TYPE.VIDEO, _participant.id);
const jitsiVideoTrack = _videoTrack?.jitsiTrack;
const videoTrackId = jitsiVideoTrack && jitsiVideoTrack.getId();
let id = `remoteVideo_${videoTrackId || ''}`;
const remoteVideo = ((document.getElementById(id): any): HTMLVideoElement);
const model = await handpose.load();
setTimeout(() => {
clearInterval(checkHighFive);
this.setState({currentParticipant: ""});
}, 60000);
checkHighFive = setInterval(()=>{
let localHand = false;
model.estimateHands(localVideo).then(predictions => {
console.log('Predictions localVideo: ',predictions);
if(predictions.length > 0 && predictions[0].handInViewConfidence > 0.5){
localHand = true;
}
});
model.estimateHands(remoteVideo).then(predictions => {
console.log('Predictions remoteVideo: ', predictions);
if(predictions.length > 0 && localHand && predictions[0].handInViewConfidence > 0.5){
this.props.dispatch(highFive(true));
setTimeout(() => { this.props.dispatch(highFive(false)); }, 4000);
this.props.conference.sendCommandOnce('HIGH_FIVE', { value:_participant.id });
clearInterval(checkHighFive);
}
});
}, 100);
}
}
}
/**
* Maps part of the Redux store to the props of this component.
*
* @param {Object} state - The Redux state.
* @param {Props} ownProps - The own props of the component.
* @returns {Props}
*/
function _mapStateToProps(state: Object, ownProps: Props): $Shape<Props> {
const tracks = state['features/base/tracks'];
const conference = getCurrentConference(state);
return {
..._abstractMapStateToProps(state, ownProps),
tracks,
conference
};
}
export default translate(connect(_mapStateToProps)(HighFiveButton));
......@@ -21,7 +21,8 @@ import {
PrivateMessageMenuButton,
RemoteControlButton,
VideoMenu,
VolumeSlider
VolumeSlider,
HighFiveButton
} from './';
declare var $: Object;
......@@ -209,6 +210,12 @@ class RemoteVideoMenuTriggerButton extends Component<Props> {
participantID = { participantID } />
);
buttons.push(
<HighFiveButton
key = 'highFive'
participantID = { participantID } />
);
if (onVolumeChange && typeof initialVolumeValue === 'number' && !isNaN(initialVolumeValue)) {
buttons.push(
<VolumeSlider
......
......@@ -18,3 +18,4 @@ export { default as VideoMenu } from './VideoMenu';
export { default as RemoteVideoMenuTriggerButton } from './RemoteVideoMenuTriggerButton';
export { default as LocalVideoMenuTriggerButton } from './LocalVideoMenuTriggerButton';
export { default as VolumeSlider } from './VolumeSlider';
export { default as HighFiveButton } from './HighFiveButton';
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment