Restructures the analytics events (#2333)
* ref: Restructures the pinned/unpinned events. * ref: Refactors the "audio only disabled" event. * ref: Refactors the "stream switch delay" event. * ref: Refactors the "select participant failed" event. * ref: Refactors the "initially muted" events. * ref: Refactors the screen sharing started/stopped events. * ref: Restructures the "device list changed" events. * ref: Restructures the "shared video" events. * ref: Restructures the "start muted" events. * ref: Restructures the "start audio only" event. * ref: Restructures the "sync track state" event. * ref: Restructures the "callkit" events. * ref: Restructures the "replace track". * ref: Restructures keyboard shortcuts events. * ref: Restructures most of the toolbar events. * ref: Refactors the API events. * ref: Restructures the video quality, profile button and invite dialog events. * ref: Refactors the "device changed" events. * ref: Refactors the page reload event. * ref: Removes an unused function. * ref: Removes a method which is needlessly exposed under a different name. * ref: Refactors the events from the remote video menu. * ref: Refactors the events from the profile pane. * ref: Restructures the recording-related events. Removes events fired when recording with something other than jibri (which isn't currently supported anyway). * ref: Cleans up AnalyticsEvents.js. * ref: Removes an unused function and adds documentation. * feat: Adds events for all API calls. * fix: Addresses feedback. * fix: Brings back mistakenly removed code. * fix: Simplifies code and fixes a bug in toggleFilmstrip when the 'visible' parameter is defined. * feat: Removes the resolution change application log. * ref: Uses consistent naming for events' attributes. Uses "_" as a separator instead of camel case or ".". * ref: Don't add the user agent and conference name as permanent properties. The library does this on its own now. * ref: Adapts the GA handler to changes in lib-jitsi-meet. * ref: Removes unused fields from the analytics handler initializaiton. * ref: Renames the google analytics file and add docs. * fix: Fixes the push-to-talk events and logs. * npm: Updates lib-jitsi-meet to 515374c8d383cb17df8ed76427e6f0fb5ea6ff1e. * fix: Fixes a recently introduced bug in the google analytics handler. * ref: Uses "value" instead of "delay" since this is friendlier to GA.
This commit is contained in:
parent
d08bbae770
commit
090f2f9ccb
2
Makefile
2
Makefile
|
@ -35,7 +35,7 @@ deploy-appbundle:
|
|||
$(BUILD_DIR)/device_selection_popup_bundle.min.map \
|
||||
$(BUILD_DIR)/alwaysontop.min.js \
|
||||
$(BUILD_DIR)/alwaysontop.min.map \
|
||||
$(OUTPUT_DIR)/analytics.js \
|
||||
$(OUTPUT_DIR)/analytics-ga.js \
|
||||
$(DEPLOY_DIR)
|
||||
|
||||
deploy-lib-jitsi-meet:
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/* global ga */
|
||||
|
||||
(function(ctx) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function Analytics() {
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Google Analytics
|
||||
*/
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-319188-14', 'jit.si');
|
||||
ga('send', 'pageview');
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {Object} - The integer to use for the 'value' of a Google
|
||||
* Analytics event.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractAction = function(event) {
|
||||
// Page events have a single 'name' field.
|
||||
if (event.type === 'page') {
|
||||
return event.name;
|
||||
}
|
||||
|
||||
// All other events have action, actionSubject, and source fields. All
|
||||
// three fields are required, and the often jitsi-meet and
|
||||
// lib-jitsi-meet use the same value when separate values are not
|
||||
// necessary (i.e. event.action == event.actionSubject).
|
||||
// Here we concatenate these three fields, but avoid adding the same
|
||||
// value twice, because it would only make the GA event's action harder
|
||||
// to read.
|
||||
let action = event.action;
|
||||
|
||||
if (event.actionSubject && event.actionSubject !== event.action) {
|
||||
// Intentionally use string concatenation as analytics needs to
|
||||
// work on IE but this file does not go through babel. For some
|
||||
// reason disabling this globally for the file does not have an
|
||||
// effect.
|
||||
// eslint-disable-next-line prefer-template
|
||||
action = event.actionSubject + '.' + action;
|
||||
}
|
||||
if (event.source && event.source !== event.action
|
||||
&& event.source !== event.action) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
action = event.source + '.' + action;
|
||||
}
|
||||
|
||||
return action;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the integer to use for a Google Analytics event's value field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {Object} - The integer to use for the 'value' of a Google
|
||||
* Analytics event, or NaN if the lib-jitsi-meet event doesn't contain a
|
||||
* suitable value.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractValue = function(event) {
|
||||
let value = event && event.attributes && event.attributes.value;
|
||||
|
||||
// Try to extract an integer from the "value" attribute.
|
||||
value = Math.round(parseFloat(value));
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts the string to use for a Google Analytics event's label field
|
||||
* from a lib-jitsi-meet analytics event.
|
||||
* @param {Object} event - The lib-jitsi-meet analytics event.
|
||||
* @returns {string} - The string to use for the 'label' of a Google
|
||||
* Analytics event.
|
||||
* @private
|
||||
*/
|
||||
Analytics.prototype._extractLabel = function(event) {
|
||||
let label = '';
|
||||
|
||||
// The label field is limited to 500B. We will concatenate all
|
||||
// attributes of the event, except the user agent because it may be
|
||||
// lengthy and is probably included from elsewhere.
|
||||
for (const property in event.attributes) {
|
||||
if (property !== 'permanent_user_agent'
|
||||
&& event.attributes.hasOwnProperty(property)) {
|
||||
// eslint-disable-next-line prefer-template
|
||||
label += property + '=' + event.attributes[property] + '&';
|
||||
}
|
||||
}
|
||||
|
||||
if (label.length > 0) {
|
||||
label = label.slice(0, -1);
|
||||
}
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the entry point of the API. The function sends an event to
|
||||
* google analytics. The format of the event is described in
|
||||
* AnalyticsAdapter in lib-jitsi-meet.
|
||||
* @param {Object} event - the event in the format specified by
|
||||
* lib-jitsi-meet.
|
||||
*/
|
||||
Analytics.prototype.sendEvent = function(event) {
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
const gaEvent = {
|
||||
'eventCategory': 'jitsi-meet',
|
||||
'eventAction': this._extractAction(event),
|
||||
'eventLabel': this._extractLabel(event)
|
||||
};
|
||||
const value = this._extractValue(event);
|
||||
|
||||
if (!isNaN(value)) {
|
||||
gaEvent.eventValue = value;
|
||||
}
|
||||
|
||||
ga('send', 'event', gaEvent);
|
||||
};
|
||||
|
||||
if (typeof ctx.JitsiMeetJS === 'undefined') {
|
||||
ctx.JitsiMeetJS = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
|
||||
ctx.JitsiMeetJS.app = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers = [];
|
||||
}
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
|
||||
})(window);
|
||||
/* eslint-enable prefer-template */
|
47
analytics.js
47
analytics.js
|
@ -1,47 +0,0 @@
|
|||
/* global ga */
|
||||
|
||||
(function(ctx) {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function Analytics() {
|
||||
/* eslint-disable */
|
||||
|
||||
/**
|
||||
* Google Analytics
|
||||
*/
|
||||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', 'UA-319188-14', 'jit.si');
|
||||
ga('send', 'pageview');
|
||||
|
||||
/* eslint-enable */
|
||||
}
|
||||
|
||||
Analytics.prototype.sendEvent = function(action, data) {
|
||||
// empty label if missing value for it and add the value,
|
||||
// the value should be integer or null
|
||||
let value = data.value;
|
||||
|
||||
value = value ? Math.round(parseFloat(value)) : null;
|
||||
const label = data.label || '';
|
||||
|
||||
// Intentionally use string concatenation as analytics needs to work on
|
||||
// IE but this file does not go through babel.
|
||||
// eslint-disable-next-line prefer-template
|
||||
ga('send', 'event', 'jit.si', action + '.' + data.browserName,
|
||||
label, value);
|
||||
};
|
||||
|
||||
if (typeof ctx.JitsiMeetJS === 'undefined') {
|
||||
ctx.JitsiMeetJS = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app === 'undefined') {
|
||||
ctx.JitsiMeetJS.app = {};
|
||||
}
|
||||
if (typeof ctx.JitsiMeetJS.app.analyticsHandlers === 'undefined') {
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers = [];
|
||||
}
|
||||
ctx.JitsiMeetJS.app.analyticsHandlers.push(Analytics);
|
||||
})(window);
|
113
conference.js
113
conference.js
|
@ -16,19 +16,13 @@ import UIUtil from './modules/UI/util/UIUtil';
|
|||
import * as JitsiMeetConferenceEvents from './ConferenceEvents';
|
||||
|
||||
import {
|
||||
CONFERENCE_AUDIO_INITIALLY_MUTED,
|
||||
CONFERENCE_SHARING_DESKTOP_START,
|
||||
CONFERENCE_SHARING_DESKTOP_STOP,
|
||||
CONFERENCE_VIDEO_INITIALLY_MUTED,
|
||||
DEVICE_LIST_CHANGED_AUDIO_MUTED,
|
||||
DEVICE_LIST_CHANGED_VIDEO_MUTED,
|
||||
SELECT_PARTICIPANT_FAILED,
|
||||
SETTINGS_CHANGE_DEVICE_AUDIO_OUT,
|
||||
SETTINGS_CHANGE_DEVICE_AUDIO_IN,
|
||||
SETTINGS_CHANGE_DEVICE_VIDEO,
|
||||
STREAM_SWITCH_DELAY,
|
||||
createDeviceChangedEvent,
|
||||
createScreenSharingEvent,
|
||||
createSelectParticipantFailedEvent,
|
||||
createStreamSwitchDelayEvent,
|
||||
createTrackMutedEvent,
|
||||
initAnalytics,
|
||||
sendAnalyticsEvent
|
||||
sendAnalytics
|
||||
} from './react/features/analytics';
|
||||
|
||||
import EventEmitter from 'events';
|
||||
|
@ -741,14 +735,13 @@ export default {
|
|||
})
|
||||
.then(([ tracks, con ]) => {
|
||||
tracks.forEach(track => {
|
||||
if (track.isAudioTrack() && this.isLocalAudioMuted()) {
|
||||
sendAnalyticsEvent(CONFERENCE_AUDIO_INITIALLY_MUTED);
|
||||
logger.log('Audio mute: initially muted');
|
||||
track.mute();
|
||||
} else if (track.isVideoTrack()
|
||||
&& this.isLocalVideoMuted()) {
|
||||
sendAnalyticsEvent(CONFERENCE_VIDEO_INITIALLY_MUTED);
|
||||
logger.log('Video mute: initially muted');
|
||||
if ((track.isAudioTrack() && this.isLocalAudioMuted())
|
||||
|| (track.isVideoTrack() && this.isLocalVideoMuted())) {
|
||||
const mediaType = track.getType();
|
||||
|
||||
sendAnalytics(
|
||||
createTrackMutedEvent(mediaType, 'initial mute'));
|
||||
logger.log(`${mediaType} mute: initially muted.`);
|
||||
track.mute();
|
||||
}
|
||||
});
|
||||
|
@ -1453,8 +1446,9 @@ export default {
|
|||
promise = createLocalTracksF({ devices: [ 'video' ] })
|
||||
.then(([ stream ]) => this.useVideoStream(stream))
|
||||
.then(() => {
|
||||
sendAnalyticsEvent(CONFERENCE_SHARING_DESKTOP_STOP);
|
||||
logger.log('switched back to local video');
|
||||
sendAnalytics(createScreenSharingEvent('stopped'));
|
||||
logger.log('Screen sharing stopped, switching to video.');
|
||||
|
||||
if (!this.localVideo && wasVideoMuted) {
|
||||
return Promise.reject('No local video to be muted!');
|
||||
} else if (wasVideoMuted && this.localVideo) {
|
||||
|
@ -1609,7 +1603,7 @@ export default {
|
|||
},
|
||||
|
||||
/**
|
||||
* Tries to switch to the screenshairng mode by disposing camera stream and
|
||||
* Tries to switch to the screensharing mode by disposing camera stream and
|
||||
* replacing it with a desktop one.
|
||||
*
|
||||
* @param {Object} [options] - Screen sharing options that will be passed to
|
||||
|
@ -1632,8 +1626,8 @@ export default {
|
|||
.then(stream => this.useVideoStream(stream))
|
||||
.then(() => {
|
||||
this.videoSwitchInProgress = false;
|
||||
sendAnalyticsEvent(CONFERENCE_SHARING_DESKTOP_START);
|
||||
logger.log('sharing local desktop');
|
||||
sendAnalytics(createScreenSharingEvent('started'));
|
||||
logger.log('Screen sharing started');
|
||||
})
|
||||
.catch(error => {
|
||||
this.videoSwitchInProgress = false;
|
||||
|
@ -1928,7 +1922,7 @@ export default {
|
|||
|
||||
room.selectParticipant(id);
|
||||
} catch (e) {
|
||||
sendAnalyticsEvent(SELECT_PARTICIPANT_FAILED);
|
||||
sendAnalytics(createSelectParticipantFailedEvent(e));
|
||||
reportError(e);
|
||||
}
|
||||
});
|
||||
|
@ -2152,22 +2146,12 @@ export default {
|
|||
APP.UI.addListener(
|
||||
UIEvents.RESOLUTION_CHANGED,
|
||||
(id, oldResolution, newResolution, delay) => {
|
||||
const logObject = {
|
||||
id: 'resolution_change',
|
||||
participant: id,
|
||||
oldValue: oldResolution,
|
||||
newValue: newResolution,
|
||||
delay
|
||||
};
|
||||
|
||||
room.sendApplicationLog(JSON.stringify(logObject));
|
||||
|
||||
// We only care about the delay between simulcast streams.
|
||||
// Longer delays will be caused by something else and will just
|
||||
// poison the data.
|
||||
if (delay < 2000) {
|
||||
sendAnalyticsEvent(STREAM_SWITCH_DELAY, { value: delay });
|
||||
}
|
||||
sendAnalytics(createStreamSwitchDelayEvent(
|
||||
{
|
||||
'old_resolution': oldResolution,
|
||||
'new_resolution': newResolution,
|
||||
value: delay
|
||||
}));
|
||||
});
|
||||
|
||||
/* eslint-enable max-params */
|
||||
|
@ -2193,7 +2177,7 @@ export default {
|
|||
cameraDeviceId => {
|
||||
const videoWasMuted = this.isLocalVideoMuted();
|
||||
|
||||
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_VIDEO);
|
||||
sendAnalytics(createDeviceChangedEvent('video', 'input'));
|
||||
createLocalTracksF({
|
||||
devices: [ 'video' ],
|
||||
cameraDeviceId,
|
||||
|
@ -2232,7 +2216,7 @@ export default {
|
|||
micDeviceId => {
|
||||
const audioWasMuted = this.isLocalAudioMuted();
|
||||
|
||||
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_AUDIO_IN);
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'input'));
|
||||
createLocalTracksF({
|
||||
devices: [ 'audio' ],
|
||||
cameraDeviceId: null,
|
||||
|
@ -2262,7 +2246,7 @@ export default {
|
|||
APP.UI.addListener(
|
||||
UIEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
|
||||
audioOutputDeviceId => {
|
||||
sendAnalyticsEvent(SETTINGS_CHANGE_DEVICE_AUDIO_OUT);
|
||||
sendAnalytics(createDeviceChangedEvent('audio', 'output'));
|
||||
APP.settings.setAudioOutputDeviceId(audioOutputDeviceId)
|
||||
.then(() => logger.log('changed audio output device'))
|
||||
.catch(err => {
|
||||
|
@ -2528,7 +2512,9 @@ export default {
|
|||
// If audio was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new audio track.
|
||||
if (audioWasMuted) {
|
||||
sendAnalyticsEvent(DEVICE_LIST_CHANGED_AUDIO_MUTED);
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'audio',
|
||||
'device list changed'));
|
||||
logger.log('Audio mute: device list changed');
|
||||
muteLocalAudio(true);
|
||||
}
|
||||
|
@ -2536,7 +2522,9 @@ export default {
|
|||
// If video was muted before, or we unplugged current device
|
||||
// and selected new one, then mute new video track.
|
||||
if (!this.isSharingScreen && videoWasMuted) {
|
||||
sendAnalyticsEvent(DEVICE_LIST_CHANGED_VIDEO_MUTED);
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'video',
|
||||
'device list changed'));
|
||||
logger.log('Video mute: device list changed');
|
||||
muteLocalVideo(true);
|
||||
}
|
||||
|
@ -2622,37 +2610,6 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Log event to callstats and analytics.
|
||||
* @param {string} name the event name
|
||||
* @param {int} value the value (it's int because google analytics supports
|
||||
* only int).
|
||||
* @param {string} label short text which provides more info about the event
|
||||
* which allows to distinguish between few event cases of the same name
|
||||
* NOTE: Should be used after conference.init
|
||||
*/
|
||||
logEvent(name, value, label) {
|
||||
sendAnalyticsEvent(name, {
|
||||
value,
|
||||
label
|
||||
});
|
||||
if (room) {
|
||||
room.sendApplicationLog(JSON.stringify({ name,
|
||||
value,
|
||||
label }));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Methods logs an application event given in the JSON format.
|
||||
* @param {string} logJSON an event to be logged in JSON format
|
||||
*/
|
||||
logJSON(logJSON) {
|
||||
if (room) {
|
||||
room.sendApplicationLog(logJSON);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disconnect from the conference and optionally request user feedback.
|
||||
* @param {boolean} [requestFeedback=false] if user feedback should be
|
||||
|
|
|
@ -307,21 +307,24 @@ var config = {
|
|||
// backToP2PDelay: 5
|
||||
},
|
||||
|
||||
// A list of scripts to load as lib-jitsi-meet "analytics handlers".
|
||||
// analyticsScriptUrls: [
|
||||
// "libs/analytics-ga.js", // google-analytics
|
||||
// "https://example.com/my-custom-analytics.js"
|
||||
// ],
|
||||
|
||||
// Information about the jitsi-meet instance we are connecting to, including
|
||||
// the user region as seen by the server.
|
||||
//
|
||||
|
||||
deploymentInfo: {
|
||||
// shard: "shard1",
|
||||
// region: "europe",
|
||||
// userRegion: "asia"
|
||||
}
|
||||
|
||||
|
||||
// List of undocumented settings used in jitsi-meet
|
||||
/**
|
||||
alwaysVisibleToolbar
|
||||
analyticsScriptUrls
|
||||
autoEnableDesktopSharing
|
||||
autoRecord
|
||||
autoRecordToken
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
|
||||
import { parseJWTFromURLParams } from '../../react/features/base/jwt';
|
||||
import {
|
||||
API_TOGGLE_AUDIO,
|
||||
API_TOGGLE_VIDEO,
|
||||
sendAnalyticsEvent
|
||||
createApiEvent,
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import { getJitsiMeetTransport } from '../transport';
|
||||
|
||||
|
@ -56,25 +55,48 @@ let videoAvailable = true;
|
|||
*/
|
||||
function initCommands() {
|
||||
commands = {
|
||||
'display-name':
|
||||
APP.conference.changeLocalDisplayName.bind(APP.conference),
|
||||
'display-name': displayName => {
|
||||
sendAnalytics(createApiEvent('display.name.changed'));
|
||||
APP.conference.changeLocalDisplayName(displayName);
|
||||
},
|
||||
'toggle-audio': () => {
|
||||
sendAnalyticsEvent(API_TOGGLE_AUDIO);
|
||||
sendAnalytics(createApiEvent('toggle-audio'));
|
||||
logger.log('Audio toggle: API command received');
|
||||
APP.conference.toggleAudioMuted(false /* no UI */);
|
||||
},
|
||||
'toggle-video': () => {
|
||||
sendAnalyticsEvent(API_TOGGLE_VIDEO);
|
||||
sendAnalytics(createApiEvent('toggle-video'));
|
||||
logger.log('Video toggle: API command received');
|
||||
APP.conference.toggleVideoMuted(false /* no UI */);
|
||||
},
|
||||
'toggle-film-strip': APP.UI.toggleFilmstrip,
|
||||
'toggle-chat': APP.UI.toggleChat,
|
||||
'toggle-contact-list': APP.UI.toggleContactList,
|
||||
'toggle-share-screen': toggleScreenSharing,
|
||||
'video-hangup': () => APP.conference.hangup(true),
|
||||
'email': APP.conference.changeLocalEmail,
|
||||
'avatar-url': APP.conference.changeLocalAvatarUrl
|
||||
'toggle-film-strip': () => {
|
||||
sendAnalytics(createApiEvent('film.strip.toggled'));
|
||||
APP.UI.toggleFilmstrip();
|
||||
},
|
||||
'toggle-chat': () => {
|
||||
sendAnalytics(createApiEvent('chat.toggled'));
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
'toggle-contact-list': () => {
|
||||
sendAnalytics(createApiEvent('contact.list.toggled'));
|
||||
APP.UI.toggleContactList();
|
||||
},
|
||||
'toggle-share-screen': () => {
|
||||
sendAnalytics(createApiEvent('screen.sharing.toggled'));
|
||||
toggleScreenSharing();
|
||||
},
|
||||
'video-hangup': () => {
|
||||
sendAnalytics(createApiEvent('video.hangup'));
|
||||
APP.conference.hangup(true);
|
||||
},
|
||||
'email': email => {
|
||||
sendAnalytics(createApiEvent('email.changed'));
|
||||
APP.conference.changeLocalEmail(email);
|
||||
},
|
||||
'avatar-url': avatarUrl => {
|
||||
sendAnalytics(createApiEvent('avatar.url.changed'));
|
||||
APP.conference.changeLocalAvatarUrl(avatarUrl);
|
||||
}
|
||||
};
|
||||
transport.on('event', ({ data, name }) => {
|
||||
if (name && commands[name]) {
|
||||
|
|
|
@ -142,6 +142,32 @@ UI.toggleFullScreen = function() {
|
|||
UIUtil.isFullScreen() ? UIUtil.exitFullScreen() : UIUtil.enterFullScreen();
|
||||
};
|
||||
|
||||
/**
|
||||
* Indicates if we're currently in full screen mode.
|
||||
*
|
||||
* @return {boolean} {true} to indicate that we're currently in full screen
|
||||
* mode, {false} otherwise
|
||||
*/
|
||||
UI.isFullScreen = function() {
|
||||
return UIUtil.isFullScreen();
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the etherpad window is currently visible.
|
||||
* @returns {Boolean} - true if the etherpad window is currently visible.
|
||||
*/
|
||||
UI.isEtherpadVisible = function() {
|
||||
return Boolean(etherpadManager && etherpadManager.isVisible());
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if there is a shared video which is being shown (?).
|
||||
* @returns {boolean} - true if there is a shared video which is being shown.
|
||||
*/
|
||||
UI.isSharedVideoShown = function() {
|
||||
return Boolean(sharedVideoManager && sharedVideoManager.isSharedVideoShown);
|
||||
};
|
||||
|
||||
/**
|
||||
* Notify user that server has shut down.
|
||||
*/
|
||||
|
|
|
@ -24,11 +24,9 @@ import {
|
|||
JitsiRecordingStatus
|
||||
} from '../../../react/features/base/lib-jitsi-meet';
|
||||
import {
|
||||
RECORDING_CANCELED,
|
||||
RECORDING_CLICKED,
|
||||
RECORDING_STARTED,
|
||||
RECORDING_STOPPED,
|
||||
sendAnalyticsEvent
|
||||
createToolbarEvent,
|
||||
createRecordingDialogEvent,
|
||||
sendAnalytics
|
||||
} from '../../../react/features/analytics';
|
||||
import { setToolboxEnabled } from '../../../react/features/toolbox';
|
||||
import { setNotificationsEnabled } from '../../../react/features/notifications';
|
||||
|
@ -452,12 +450,13 @@ const Recording = {
|
|||
},
|
||||
|
||||
// checks whether recording is enabled and whether we have params
|
||||
// to start automatically recording
|
||||
// to start automatically recording (XXX: No, it doesn't do that).
|
||||
checkAutoRecord() {
|
||||
if (_isRecordingButtonEnabled && config.autoRecord) {
|
||||
this.predefinedToken = UIUtil.escapeHtml(config.autoRecordToken);
|
||||
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED,
|
||||
this.predefinedToken);
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ token: this.predefinedToken });
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -467,11 +466,16 @@ const Recording = {
|
|||
* @returns {void}
|
||||
*/
|
||||
_onToolbarButtonClick() {
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'recording.button',
|
||||
{
|
||||
'dialog_present': Boolean(dialog)
|
||||
}));
|
||||
|
||||
if (dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendAnalyticsEvent(RECORDING_CLICKED);
|
||||
switch (this.currentState) {
|
||||
case JitsiRecordingStatus.ON:
|
||||
case JitsiRecordingStatus.RETRYING:
|
||||
|
@ -479,7 +483,13 @@ const Recording = {
|
|||
_showStopRecordingPrompt(this.recordingType).then(
|
||||
() => {
|
||||
this.eventEmitter.emit(UIEvents.RECORDING_TOGGLED);
|
||||
sendAnalyticsEvent(RECORDING_STOPPED);
|
||||
|
||||
// The confirm button on the stop recording dialog was
|
||||
// clicked
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent(
|
||||
'stop',
|
||||
'confirm.button'));
|
||||
},
|
||||
() => {}); // eslint-disable-line no-empty-function
|
||||
break;
|
||||
|
@ -492,21 +502,32 @@ const Recording = {
|
|||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ streamId });
|
||||
sendAnalyticsEvent(RECORDING_STARTED);
|
||||
|
||||
// The confirm button on the start recording dialog was
|
||||
// clicked
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent(
|
||||
'start',
|
||||
'confirm.button'));
|
||||
})
|
||||
.catch(reason => {
|
||||
if (reason === APP.UI.messageHandler.CANCEL) {
|
||||
sendAnalyticsEvent(RECORDING_CANCELED);
|
||||
// The cancel button on the start recording dialog was
|
||||
// clicked
|
||||
sendAnalytics(
|
||||
createRecordingDialogEvent(
|
||||
'start',
|
||||
'cancel.button'));
|
||||
} else {
|
||||
logger.error(reason);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Note that we only fire analytics events for Jibri.
|
||||
if (this.predefinedToken) {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ token: this.predefinedToken });
|
||||
sendAnalyticsEvent(RECORDING_STARTED);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -515,12 +536,9 @@ const Recording = {
|
|||
this.eventEmitter.emit(
|
||||
UIEvents.RECORDING_TOGGLED,
|
||||
{ token });
|
||||
sendAnalyticsEvent(RECORDING_STARTED);
|
||||
})
|
||||
.catch(reason => {
|
||||
if (reason === APP.UI.messageHandler.CANCEL) {
|
||||
sendAnalyticsEvent(RECORDING_CANCELED);
|
||||
} else {
|
||||
if (reason !== APP.UI.messageHandler.CANCEL) {
|
||||
logger.error(reason);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -11,15 +11,8 @@ import LargeContainer from '../videolayout/LargeContainer';
|
|||
import Filmstrip from '../videolayout/Filmstrip';
|
||||
|
||||
import {
|
||||
SHARED_VIDEO_ALREADY_SHARED,
|
||||
SHARED_VIDEO_AUDIO_MUTED,
|
||||
SHARED_VIDEO_AUDIO_UNMUTED,
|
||||
SHARED_VIDEO_CANCELED,
|
||||
SHARED_VIDEO_PAUSED,
|
||||
SHARED_VIDEO_STARTED,
|
||||
SHARED_VIDEO_STOPPED,
|
||||
SHARED_VIDEO_VOLUME_CHANGED,
|
||||
sendAnalyticsEvent
|
||||
createSharedVideoEvent as createEvent,
|
||||
sendAnalytics
|
||||
} from '../../../react/features/analytics';
|
||||
import {
|
||||
participantJoined,
|
||||
|
@ -95,11 +88,11 @@ export default class SharedVideoManager {
|
|||
url => {
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, url, 'start');
|
||||
sendAnalyticsEvent(SHARED_VIDEO_STARTED);
|
||||
sendAnalytics(createEvent('started'));
|
||||
},
|
||||
err => {
|
||||
logger.log('SHARED VIDEO CANCELED', err);
|
||||
sendAnalyticsEvent(SHARED_VIDEO_CANCELED);
|
||||
sendAnalytics(createEvent('canceled'));
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -119,7 +112,7 @@ export default class SharedVideoManager {
|
|||
}
|
||||
this.emitter.emit(
|
||||
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop');
|
||||
sendAnalyticsEvent(SHARED_VIDEO_STOPPED);
|
||||
sendAnalytics(createEvent('stopped'));
|
||||
},
|
||||
() => {}); // eslint-disable-line no-empty-function
|
||||
} else {
|
||||
|
@ -127,7 +120,7 @@ export default class SharedVideoManager {
|
|||
descriptionKey: 'dialog.alreadySharedVideoMsg',
|
||||
titleKey: 'dialog.alreadySharedVideoTitle'
|
||||
});
|
||||
sendAnalyticsEvent(SHARED_VIDEO_ALREADY_SHARED);
|
||||
sendAnalytics(createEvent('already.shared'));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,7 +229,7 @@ export default class SharedVideoManager {
|
|||
// eslint-disable-next-line eqeqeq
|
||||
} else if (event.data == YT.PlayerState.PAUSED) {
|
||||
self.smartAudioUnmute();
|
||||
sendAnalyticsEvent(SHARED_VIDEO_PAUSED);
|
||||
sendAnalytics(createEvent('paused'));
|
||||
}
|
||||
// eslint-disable-next-line eqeqeq
|
||||
self.fireSharedVideoEvent(event.data == YT.PlayerState.PAUSED);
|
||||
|
@ -268,7 +261,12 @@ export default class SharedVideoManager {
|
|||
} else if (event.data.volume <= 0 || event.data.muted) {
|
||||
self.smartAudioUnmute();
|
||||
}
|
||||
sendAnalyticsEvent(SHARED_VIDEO_VOLUME_CHANGED);
|
||||
sendAnalytics(createEvent(
|
||||
'volume.changed',
|
||||
{
|
||||
volume: event.data.volume,
|
||||
muted: event.data.muted
|
||||
}));
|
||||
};
|
||||
|
||||
window.onPlayerReady = function(event) {
|
||||
|
@ -434,8 +432,8 @@ export default class SharedVideoManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates video, if its not playing and needs starting or
|
||||
* if its playing and needs to be paysed
|
||||
* Updates video, if it's not playing and needs starting or if it's playing
|
||||
* and needs to be paused.
|
||||
* @param id the id of the sender of the command
|
||||
* @param url the video url
|
||||
* @param attributes
|
||||
|
@ -574,7 +572,7 @@ export default class SharedVideoManager {
|
|||
if (APP.conference.isLocalAudioMuted()
|
||||
&& !this.mutedWithUserInteraction
|
||||
&& !this.isSharedVideoVolumeOn()) {
|
||||
sendAnalyticsEvent(SHARED_VIDEO_AUDIO_UNMUTED);
|
||||
sendAnalytics(createEvent('audio.unmuted'));
|
||||
logger.log('Shared video: audio unmuted');
|
||||
this.emitter.emit(UIEvents.AUDIO_MUTED, false, false);
|
||||
this.showMicMutedPopup(false);
|
||||
|
@ -588,7 +586,7 @@ export default class SharedVideoManager {
|
|||
smartAudioMute() {
|
||||
if (!APP.conference.isLocalAudioMuted()
|
||||
&& this.isSharedVideoVolumeOn()) {
|
||||
sendAnalyticsEvent(SHARED_VIDEO_AUDIO_MUTED);
|
||||
sendAnalytics(createEvent('audio.muted'));
|
||||
logger.log('Shared video: audio muted');
|
||||
this.emitter.emit(UIEvents.AUDIO_MUTED, true, false);
|
||||
this.showMicMutedPopup(true);
|
||||
|
|
|
@ -4,9 +4,8 @@ import UIEvents from '../../../../service/UI/UIEvents';
|
|||
import Settings from '../../../settings/Settings';
|
||||
|
||||
import {
|
||||
AUTHENTICATE_LOGIN_CLICKED,
|
||||
AUTHENTICATE_LOGOUT_CLICKED,
|
||||
sendAnalyticsEvent
|
||||
createProfilePanelButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../../../react/features/analytics';
|
||||
|
||||
const sidePanelsContainerId = 'sideToolbarContainer';
|
||||
|
@ -95,7 +94,7 @@ export default {
|
|||
*
|
||||
*/
|
||||
function loginClicked() {
|
||||
sendAnalyticsEvent(AUTHENTICATE_LOGIN_CLICKED);
|
||||
sendAnalytics(createProfilePanelButtonEvent('login.button'));
|
||||
emitter.emit(UIEvents.AUTH_CLICKED);
|
||||
}
|
||||
|
||||
|
@ -108,7 +107,7 @@ export default {
|
|||
const titleKey = 'dialog.logoutTitle';
|
||||
const msgKey = 'dialog.logoutQuestion';
|
||||
|
||||
sendAnalyticsEvent(AUTHENTICATE_LOGOUT_CLICKED);
|
||||
sendAnalytics(createProfilePanelButtonEvent('logout.button'));
|
||||
|
||||
// Ask for confirmation
|
||||
APP.UI.messageHandler.openTwoButtonDialog({
|
||||
|
|
|
@ -6,8 +6,9 @@ import UIEvents from '../../../service/UI/UIEvents';
|
|||
import UIUtil from '../util/UIUtil';
|
||||
|
||||
import {
|
||||
TOOLBAR_FILMSTRIP_TOGGLED,
|
||||
sendAnalyticsEvent
|
||||
createShortcutEvent,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../../react/features/analytics';
|
||||
|
||||
const Filmstrip = {
|
||||
|
@ -75,8 +76,18 @@ const Filmstrip = {
|
|||
// Firing the event instead of executing toggleFilmstrip method because
|
||||
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
|
||||
// to correctly resize the video area.
|
||||
$('#toggleFilmstripButton').on('click',
|
||||
() => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP));
|
||||
$('#toggleFilmstripButton').on(
|
||||
'click',
|
||||
() => {
|
||||
// The 'enable' parameter is set to true if the action results
|
||||
// in the filmstrip being hidden.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.filmstrip.button',
|
||||
{
|
||||
enable: this.isFilmstripVisible()
|
||||
}));
|
||||
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
|
||||
});
|
||||
|
||||
this._registerToggleFilmstripShortcut();
|
||||
},
|
||||
|
@ -94,7 +105,14 @@ const Filmstrip = {
|
|||
// Firing the event instead of executing toggleFilmstrip method because
|
||||
// it's important to hide the filmstrip by UI.toggleFilmstrip in order
|
||||
// to correctly resize the video area.
|
||||
const handler = () => this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
|
||||
const handler = () => {
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.filmstrip',
|
||||
{
|
||||
enable: this.isFilmstripVisible()
|
||||
}));
|
||||
this.eventEmitter.emit(UIEvents.TOGGLE_FILMSTRIP);
|
||||
};
|
||||
|
||||
APP.keyboardshortcut.registerShortcut(
|
||||
shortcut,
|
||||
|
@ -129,50 +147,43 @@ const Filmstrip = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the filmstrip.
|
||||
* Toggles the visibility of the filmstrip, or sets it to a specific value
|
||||
* if the 'visible' parameter is specified.
|
||||
*
|
||||
* @param visible optional {Boolean} which specifies the desired visibility
|
||||
* of the filmstrip. If not specified, the visibility will be flipped
|
||||
* (i.e. toggled); otherwise, the visibility will be set to the specified
|
||||
* value.
|
||||
* @param {Boolean} sendAnalytics - True to send an analytics event. The
|
||||
* default value is true.
|
||||
*
|
||||
* Note:
|
||||
* This method shouldn't be executed directly to hide the filmstrip.
|
||||
* It's important to hide the filmstrip with UI.toggleFilmstrip in order
|
||||
* to correctly resize the video area.
|
||||
*/
|
||||
toggleFilmstrip(visible, sendAnalytics = true) {
|
||||
const isVisibleDefined = typeof visible === 'boolean';
|
||||
toggleFilmstrip(visible) {
|
||||
const wasFilmstripVisible = this.isFilmstripVisible();
|
||||
|
||||
if (!isVisibleDefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
visible = this.isFilmstripVisible();
|
||||
} else if (this.isFilmstripVisible() === visible) {
|
||||
// If 'visible' is defined and matches the current state, we have
|
||||
// nothing to do. Otherwise (regardless of whether 'visible' is defined)
|
||||
// we need to toggle the state.
|
||||
if (visible === wasFilmstripVisible) {
|
||||
return;
|
||||
}
|
||||
if (sendAnalytics) {
|
||||
sendAnalyticsEvent(TOOLBAR_FILMSTRIP_TOGGLED);
|
||||
}
|
||||
|
||||
this.filmstrip.toggleClass('hidden');
|
||||
|
||||
if (visible) {
|
||||
if (wasFilmstripVisible) {
|
||||
this.showMenuUpIcon();
|
||||
} else {
|
||||
this.showMenuDownIcon();
|
||||
}
|
||||
|
||||
// Emit/fire UIEvents.TOGGLED_FILMSTRIP.
|
||||
const eventEmitter = this.eventEmitter;
|
||||
const isFilmstripVisible = this.isFilmstripVisible();
|
||||
|
||||
if (eventEmitter) {
|
||||
eventEmitter.emit(
|
||||
if (this.eventEmitter) {
|
||||
this.eventEmitter.emit(
|
||||
UIEvents.TOGGLED_FILMSTRIP,
|
||||
this.isFilmstripVisible());
|
||||
!wasFilmstripVisible);
|
||||
}
|
||||
APP.store.dispatch(setFilmstripVisibility(isFilmstripVisible));
|
||||
APP.store.dispatch(setFilmstripVisibility(!wasFilmstripVisible));
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
|
||||
import { toggleDialog } from '../../react/features/base/dialog';
|
||||
import {
|
||||
SHORTCUT_HELP,
|
||||
SHORTCUT_SPEAKER_STATS_CLICKED,
|
||||
SHORTCUT_TALK_CLICKED,
|
||||
SHORTCUT_TALK_RELEASED,
|
||||
sendAnalyticsEvent
|
||||
ACTION_SHORTCUT_PRESSED as PRESSED,
|
||||
ACTION_SHORTCUT_RELEASED as RELEASED,
|
||||
createShortcutEvent,
|
||||
sendAnalytics
|
||||
} from '../../react/features/analytics';
|
||||
import { KeyboardShortcutsDialog }
|
||||
from '../../react/features/keyboard-shortcuts';
|
||||
|
@ -72,8 +71,10 @@ const KeyboardShortcut = {
|
|||
|| $(':focus').is('textarea'))) {
|
||||
if (this._getKeyboardKey(e).toUpperCase() === ' ') {
|
||||
if (APP.conference.isLocalAudioMuted()) {
|
||||
sendAnalyticsEvent(SHORTCUT_TALK_RELEASED);
|
||||
logger.log('Talk shortcut released');
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'push.to.talk',
|
||||
PRESSED));
|
||||
logger.log('Talk shortcut pressed');
|
||||
APP.conference.muteAudio(false);
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +94,7 @@ const KeyboardShortcut = {
|
|||
* Registers a new shortcut.
|
||||
*
|
||||
* @param shortcutChar the shortcut character triggering the action
|
||||
* @param shortcutAttr the "shortcut" html element attribute mappring an
|
||||
* @param shortcutAttr the "shortcut" html element attribute mapping an
|
||||
* element to this shortcut and used to show the shortcut character on the
|
||||
* element tooltip
|
||||
* @param exec the function to be executed when the shortcut is pressed
|
||||
|
@ -175,7 +176,7 @@ const KeyboardShortcut = {
|
|||
*/
|
||||
_initGlobalShortcuts() {
|
||||
this.registerShortcut('?', null, () => {
|
||||
sendAnalyticsEvent(SHORTCUT_HELP);
|
||||
sendAnalytics(createShortcutEvent('help'));
|
||||
APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
|
||||
shortcutDescriptions: _shortcutsHelp
|
||||
}));
|
||||
|
@ -184,15 +185,15 @@ const KeyboardShortcut = {
|
|||
// register SPACE shortcut in two steps to insure visibility of help
|
||||
// message
|
||||
this.registerShortcut(' ', null, () => {
|
||||
sendAnalyticsEvent(SHORTCUT_TALK_CLICKED);
|
||||
logger.log('Talk shortcut pressed');
|
||||
sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
|
||||
logger.log('Talk shortcut released');
|
||||
APP.conference.muteAudio(true);
|
||||
});
|
||||
this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
|
||||
|
||||
if (!interfaceConfig.filmStripOnly) {
|
||||
this.registerShortcut('T', null, () => {
|
||||
sendAnalyticsEvent(SHORTCUT_SPEAKER_STATS_CLICKED);
|
||||
sendAnalytics(createShortcutEvent('speaker.stats'));
|
||||
APP.store.dispatch(toggleDialog(SpeakerStats, {
|
||||
conference: APP.conference
|
||||
}));
|
||||
|
|
|
@ -37,20 +37,20 @@ export default class JitsiMeetLogStorage {
|
|||
return;
|
||||
}
|
||||
|
||||
let logJSON = `{"log${this.counter}":"\n`;
|
||||
let logMessage = `{"log${this.counter}":"\n`;
|
||||
|
||||
for (let i = 0, len = logEntries.length; i < len; i++) {
|
||||
const logEntry = logEntries[i];
|
||||
|
||||
if (typeof logEntry === 'object') {
|
||||
// Aggregated message
|
||||
logJSON += `(${logEntry.count}) ${logEntry.text}\n`;
|
||||
logMessage += `(${logEntry.count}) ${logEntry.text}\n`;
|
||||
} else {
|
||||
// Regular message
|
||||
logJSON += `${logEntry}\n`;
|
||||
logMessage += `${logEntry}\n`;
|
||||
}
|
||||
}
|
||||
logJSON += '"}';
|
||||
logMessage += '"}';
|
||||
|
||||
this.counter += 1;
|
||||
|
||||
|
@ -58,11 +58,11 @@ export default class JitsiMeetLogStorage {
|
|||
// on the way that could be uninitialized if the storeLogs
|
||||
// attempt would be made very early (which is unlikely)
|
||||
try {
|
||||
APP.conference.logJSON(logJSON);
|
||||
APP.conference.room.sendApplicationLog(logMessage);
|
||||
} catch (error) {
|
||||
// NOTE console is intentional here
|
||||
console.error(
|
||||
'Failed to store the logs: ', logJSON, error);
|
||||
'Failed to store the logs: ', logMessage, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6386,7 +6386,7 @@
|
|||
}
|
||||
},
|
||||
"lib-jitsi-meet": {
|
||||
"version": "github:jitsi/lib-jitsi-meet#c7d6d158b9ab87f47b2bb8484565bcb17e687f7e",
|
||||
"version": "github:jitsi/lib-jitsi-meet#515374c8d383cb17df8ed76427e6f0fb5ea6ff1e",
|
||||
"requires": {
|
||||
"async": "0.9.0",
|
||||
"current-executing-script": "0.1.3",
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
"jquery-i18next": "1.2.0",
|
||||
"js-md5": "0.6.1",
|
||||
"jwt-decode": "2.2.0",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#c7d6d158b9ab87f47b2bb8484565bcb17e687f7e",
|
||||
"lib-jitsi-meet": "github:jitsi/lib-jitsi-meet#515374c8d383cb17df8ed76427e6f0fb5ea6ff1e",
|
||||
"lodash": "4.17.4",
|
||||
"moment": "2.19.4",
|
||||
"nuclear-js": "1.4.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -9,12 +9,14 @@ import { getJitsiMeetGlobalNS, loadScript } from '../base/util';
|
|||
const logger = require('jitsi-meet-logger').getLogger(__filename);
|
||||
|
||||
/**
|
||||
* Sends an analytics event.
|
||||
* Sends an event through the lib-jitsi-meet AnalyticsAdapter interface.
|
||||
*
|
||||
* @inheritdoc
|
||||
* @param {Object} event - The event to send. It should be formatted as
|
||||
* described in AnalyticsAdapter.js in lib-jitsi-meet.
|
||||
* @returns {void}
|
||||
*/
|
||||
export function sendAnalyticsEvent(...args: Array<any>) {
|
||||
analytics.sendEvent(...args);
|
||||
export function sendAnalytics(event: Object) {
|
||||
analytics.sendEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -38,23 +40,17 @@ export function initAnalytics({ getState }: { getState: Function }) {
|
|||
const state = getState();
|
||||
const config = state['features/base/config'];
|
||||
const { analyticsScriptUrls } = config;
|
||||
const machineId = JitsiMeetJS.getMachineId();
|
||||
const { user } = state['features/base/jwt'];
|
||||
const handlerConstructorOptions = {
|
||||
product: 'lib-jitsi-meet',
|
||||
version: JitsiMeetJS.version,
|
||||
session: machineId,
|
||||
user: user ? user.id : `uid-${machineId}`,
|
||||
server: state['features/base/connection'].locationURL.host
|
||||
user
|
||||
};
|
||||
|
||||
_loadHandlers(analyticsScriptUrls, handlerConstructorOptions)
|
||||
.then(handlers => {
|
||||
const permanentProperties: Object = {
|
||||
roomName: state['features/base/conference'].room,
|
||||
userAgent: navigator.userAgent
|
||||
};
|
||||
const roomName = state['features/base/conference'].room;
|
||||
const { group, server } = state['features/base/jwt'];
|
||||
const permanentProperties = {};
|
||||
|
||||
if (server) {
|
||||
permanentProperties.server = server;
|
||||
|
@ -76,6 +72,9 @@ export function initAnalytics({ getState }: { getState: Function }) {
|
|||
}
|
||||
|
||||
analytics.addPermanentProperties(permanentProperties);
|
||||
analytics.setConferenceName(roomName);
|
||||
|
||||
// Set the handlers last, since this triggers emptying of the cache
|
||||
analytics.setAnalyticsHandlers(handlers);
|
||||
},
|
||||
error => analytics.dispose() && logger.error(error));
|
||||
|
|
|
@ -3,9 +3,8 @@
|
|||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
START_MUTED_SERVER_AUDIO_,
|
||||
START_MUTED_SERVER_VIDEO_,
|
||||
sendAnalyticsEvent
|
||||
createStartMutedConfigurationEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { getName } from '../../app';
|
||||
import { JitsiConferenceEvents } from '../lib-jitsi-meet';
|
||||
|
@ -90,12 +89,8 @@ function _addConferenceListeners(conference, dispatch) {
|
|||
const audioMuted = Boolean(conference.startAudioMuted);
|
||||
const videoMuted = Boolean(conference.startVideoMuted);
|
||||
|
||||
sendAnalyticsEvent(
|
||||
`${START_MUTED_SERVER_AUDIO_}.${
|
||||
audioMuted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalyticsEvent(
|
||||
`${START_MUTED_SERVER_VIDEO_}.${
|
||||
videoMuted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createStartMutedConfigurationEvent(
|
||||
'remote', audioMuted, videoMuted));
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${
|
||||
videoMuted ? 'video' : ''}`);
|
||||
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
|
||||
import {
|
||||
_LOCAL,
|
||||
_REMOTE,
|
||||
AUDIO_ONLY_DISABLED,
|
||||
PINNED_,
|
||||
UNPINNED_,
|
||||
sendAnalyticsEvent
|
||||
ACTION_PINNED,
|
||||
ACTION_UNPINNED,
|
||||
createAudioOnlyDisableEvent,
|
||||
createPinnedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { CONNECTION_ESTABLISHED } from '../connection';
|
||||
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../media';
|
||||
|
@ -131,7 +130,7 @@ function _conferenceFailedOrLeft({ dispatch, getState }, next, action) {
|
|||
const result = next(action);
|
||||
|
||||
if (getState()['features/base/conference'].audioOnly) {
|
||||
sendAnalyticsEvent(AUDIO_ONLY_DISABLED);
|
||||
sendAnalytics(createAudioOnlyDisableEvent());
|
||||
logger.log('Audio only disabled');
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
|
@ -193,19 +192,19 @@ function _pinParticipant(store, next, action) {
|
|||
|
||||
if (typeof APP !== 'undefined') {
|
||||
const pinnedParticipant = getPinnedParticipant(participants);
|
||||
const actionName = action.participant.id ? PINNED_ : UNPINNED_;
|
||||
let videoType;
|
||||
const actionName
|
||||
= action.participant.id ? ACTION_PINNED : ACTION_UNPINNED;
|
||||
const local = (participantById && participantById.local)
|
||||
|| (!id && pinnedParticipant && pinnedParticipant.local);
|
||||
|
||||
if ((participantById && participantById.local)
|
||||
|| (!id && pinnedParticipant && pinnedParticipant.local)) {
|
||||
videoType = _LOCAL;
|
||||
} else {
|
||||
videoType = _REMOTE;
|
||||
}
|
||||
sendAnalytics(createPinnedEvent(
|
||||
actionName,
|
||||
local ? 'local' : id,
|
||||
{
|
||||
'participant_count': conference.getParticipantCount(),
|
||||
local
|
||||
}));
|
||||
|
||||
sendAnalyticsEvent(
|
||||
`${actionName}.${videoType}`,
|
||||
{ value: conference.getParticipantCount() });
|
||||
}
|
||||
|
||||
// The following condition prevents signaling to pin local participant and
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
/* @flow */
|
||||
|
||||
import {
|
||||
START_AUDIO_ONLY_,
|
||||
START_MUTED_CLIENT_AUDIO_,
|
||||
START_MUTED_CLIENT_VIDEO_,
|
||||
SYNC_TRACK_STATE_,
|
||||
sendAnalyticsEvent
|
||||
createStartAudioOnlyEvent,
|
||||
createStartMutedConfigurationEvent,
|
||||
createSyncTrackStateEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { SET_ROOM, setAudioOnly } from '../conference';
|
||||
import { parseURLParams } from '../config';
|
||||
|
@ -90,12 +89,8 @@ function _setRoom({ dispatch, getState }, next, action) {
|
|||
audioMuted = Boolean(audioMuted);
|
||||
videoMuted = Boolean(videoMuted);
|
||||
|
||||
// Apply the config.
|
||||
|
||||
sendAnalyticsEvent(
|
||||
`${START_MUTED_CLIENT_AUDIO_}.${audioMuted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalyticsEvent(
|
||||
`${START_MUTED_CLIENT_VIDEO_}.${videoMuted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createStartMutedConfigurationEvent(
|
||||
'local', audioMuted, videoMuted));
|
||||
|
||||
logger.log(`Start muted: ${audioMuted ? 'audio, ' : ''}${
|
||||
videoMuted ? 'video' : ''}`);
|
||||
|
@ -128,8 +123,7 @@ function _setRoom({ dispatch, getState }, next, action) {
|
|||
audioOnly = true;
|
||||
}
|
||||
|
||||
sendAnalyticsEvent(
|
||||
`${START_AUDIO_ONLY_}.${audioOnly ? 'enabled' : 'disabled'}`);
|
||||
sendAnalytics(createStartAudioOnlyEvent(audioOnly));
|
||||
logger.log(`Start audio only set to ${audioOnly.toString()}`);
|
||||
dispatch(setAudioOnly(audioOnly));
|
||||
}
|
||||
|
@ -155,9 +149,7 @@ function _syncTrackMutedState({ getState }, track) {
|
|||
// not yet in redux state and JitsiTrackEvents.TRACK_MUTE_CHANGED may be
|
||||
// fired before track gets to state.
|
||||
if (track.muted !== muted) {
|
||||
sendAnalyticsEvent(
|
||||
`${SYNC_TRACK_STATE_}.${track.mediaType}.${
|
||||
muted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createSyncTrackStateEvent(track.mediaType, muted));
|
||||
logger.log(`Sync ${track.mediaType} track muted state to ${
|
||||
muted ? 'muted' : 'unmuted'}`);
|
||||
track.muted = muted;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
REPLACE_TRACK_,
|
||||
sendAnalyticsEvent
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { JitsiTrackErrors, JitsiTrackEvents } from '../lib-jitsi-meet';
|
||||
import {
|
||||
|
@ -220,9 +220,10 @@ export function replaceLocalTrack(oldTrack, newTrack, conference) {
|
|||
: setAudioMuted;
|
||||
const isMuted = newTrack.isMuted();
|
||||
|
||||
sendAnalyticsEvent(`${REPLACE_TRACK_}.${
|
||||
newTrack.getType()}.${
|
||||
isMuted ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
newTrack.getType(),
|
||||
'track.replaced',
|
||||
isMuted));
|
||||
logger.log(`Replace ${newTrack.getType()} track - ${
|
||||
isMuted ? 'muted' : 'unmuted'}`);
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
FEEDBACK_OPEN,
|
||||
sendAnalyticsEvent
|
||||
createFeedbackOpenEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
@ -148,7 +148,7 @@ class FeedbackDialog extends Component {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentDidMount() {
|
||||
sendAnalyticsEvent(FEEDBACK_OPEN);
|
||||
sendAnalytics(createFeedbackOpenEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,7 +26,7 @@ MiddlewareRegistry.register(({ getState }) => next => action => {
|
|||
// not need the middleware implemented here, Filmstrip.init, and
|
||||
// UI.start.
|
||||
|| (Filmstrip.filmstrip
|
||||
&& Filmstrip.toggleFilmstrip(!newValue, false));
|
||||
&& Filmstrip.toggleFilmstrip(!newValue));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TOOLBAR_INVITE_CLOSE,
|
||||
sendAnalyticsEvent
|
||||
createInviteDialogClosedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { getInviteURL } from '../../base/connection';
|
||||
import { Dialog } from '../../base/dialog';
|
||||
|
@ -54,7 +54,7 @@ class InviteDialog extends Component {
|
|||
* @inheritdoc
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
sendAnalyticsEvent(TOOLBAR_INVITE_CLOSE);
|
||||
sendAnalytics(createInviteDialogClosedEvent());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
/* @flow */
|
||||
|
||||
import {
|
||||
CALLKIT_BACKGROUND_VIDEO_MUTED,
|
||||
sendAnalyticsEvent
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { setLastN } from '../../base/conference';
|
||||
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../../base/media';
|
||||
|
@ -46,7 +46,9 @@ export function _setBackgroundVideoMuted(muted: boolean) {
|
|||
|
||||
audioOnly || dispatch(setLastN(muted ? 0 : undefined));
|
||||
|
||||
sendAnalyticsEvent(CALLKIT_BACKGROUND_VIDEO_MUTED);
|
||||
sendAnalytics(createTrackMutedEvent(
|
||||
'video',
|
||||
'callkit.background.video'));
|
||||
|
||||
dispatch(setVideoMuted(muted, VIDEO_MUTISM_AUTHORITY.BACKGROUND));
|
||||
};
|
||||
|
|
|
@ -4,8 +4,8 @@ import { NativeModules } from 'react-native';
|
|||
import uuid from 'uuid';
|
||||
|
||||
import {
|
||||
CALLKIT_AUDIO_,
|
||||
sendAnalyticsEvent
|
||||
createTrackMutedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT, appNavigate } from '../../app';
|
||||
import {
|
||||
|
@ -279,8 +279,7 @@ function _onPerformSetMutedCallAction({ callUUID, muted: newValue }) {
|
|||
if (oldValue !== newValue) {
|
||||
const value = Boolean(newValue);
|
||||
|
||||
sendAnalyticsEvent(`${CALLKIT_AUDIO_}.${
|
||||
value ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createTrackMutedEvent('audio', 'callkit', value));
|
||||
dispatch(setAudioMuted(value));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import { PAGE_RELOAD } from '../../analytics';
|
||||
import {
|
||||
createPageReloadScheduledEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import {
|
||||
isFatalJitsiConferenceError,
|
||||
isFatalJitsiConnectionError
|
||||
|
@ -159,11 +162,17 @@ export default class AbstractPageReloadOverlay extends Component<*, *> {
|
|||
// sent to the backed.
|
||||
// FIXME: We should dispatch action for this.
|
||||
if (typeof APP !== 'undefined') {
|
||||
APP.conference.logEvent(
|
||||
PAGE_RELOAD,
|
||||
/* value */ undefined,
|
||||
/* label */ this.props.reason);
|
||||
if (APP.conference && APP.conference.room) {
|
||||
APP.conference.room.sendApplicationLog(JSON.stringify(
|
||||
{
|
||||
name: 'page.reload',
|
||||
label: this.props.reason
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
sendAnalytics(createPageReloadScheduledEvent(
|
||||
this.props.reason, this.state.timeoutSeconds));
|
||||
|
||||
logger.info(
|
||||
`The conference will be reloaded after ${
|
||||
|
|
|
@ -3,8 +3,8 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
REMOTE_VIDEO_MENU_KICK,
|
||||
sendAnalyticsEvent
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { kickParticipant } from '../../base/participants';
|
||||
|
@ -86,13 +86,12 @@ class KickButton extends Component {
|
|||
_onClick() {
|
||||
const { dispatch, onClick, participantID } = this.props;
|
||||
|
||||
sendAnalyticsEvent(
|
||||
REMOTE_VIDEO_MENU_KICK,
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'kick.button',
|
||||
{
|
||||
value: 1,
|
||||
label: participantID
|
||||
}
|
||||
);
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(kickParticipant(participantID));
|
||||
|
||||
if (onClick) {
|
||||
|
|
|
@ -3,8 +3,8 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
REMOTE_VIDEO_MENU_MUTE_CLICKED,
|
||||
sendAnalyticsEvent
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
import { openDialog } from '../../base/dialog';
|
||||
|
@ -101,13 +101,11 @@ class MuteButton extends Component {
|
|||
_onClick() {
|
||||
const { dispatch, onClick, participantID } = this.props;
|
||||
|
||||
sendAnalyticsEvent(
|
||||
REMOTE_VIDEO_MENU_MUTE_CLICKED,
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'mute.button',
|
||||
{
|
||||
value: 1,
|
||||
label: participantID
|
||||
}
|
||||
);
|
||||
'participant_id': participantID
|
||||
}));
|
||||
|
||||
dispatch(openDialog(MuteRemoteParticipantDialog, { participantID }));
|
||||
|
||||
|
|
|
@ -6,8 +6,8 @@ import { Dialog } from '../../base/dialog';
|
|||
import { translate } from '../../base/i18n';
|
||||
|
||||
import {
|
||||
REMOTE_VIDEO_MENU_MUTE_CONFIRMED,
|
||||
sendAnalyticsEvent
|
||||
createRemoteMuteConfirmedEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { muteRemoteParticipant } from '../../base/participants';
|
||||
|
||||
|
@ -77,18 +77,12 @@ class MuteRemoteParticipantDialog extends Component {
|
|||
* Handles the submit button action.
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @returns {boolean} - True (to note that the modal should be closed).
|
||||
*/
|
||||
_onSubmit() {
|
||||
const { dispatch, participantID } = this.props;
|
||||
|
||||
sendAnalyticsEvent(
|
||||
REMOTE_VIDEO_MENU_MUTE_CONFIRMED,
|
||||
{
|
||||
value: 1,
|
||||
label: participantID
|
||||
}
|
||||
);
|
||||
sendAnalytics(createRemoteMuteConfirmedEvent(participantID));
|
||||
|
||||
dispatch(muteRemoteParticipant(participantID));
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
|
||||
import {
|
||||
REMOTE_VIDEO_MENU_REMOTE_CONTROL_,
|
||||
sendAnalyticsEvent
|
||||
createRemoteVideoMenuButtonEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { translate } from '../../base/i18n';
|
||||
|
||||
|
@ -122,24 +122,19 @@ class RemoteControlButton extends Component {
|
|||
_onClick() {
|
||||
const { onClick, participantID, remoteControlState } = this.props;
|
||||
|
||||
let eventName;
|
||||
// TODO: What do we do in case the state is e.g. "requesting"?
|
||||
if (remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED
|
||||
|| remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) {
|
||||
|
||||
if (remoteControlState === REMOTE_CONTROL_MENU_STATES.STARTED) {
|
||||
eventName = 'stop';
|
||||
}
|
||||
const enable
|
||||
= remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED;
|
||||
|
||||
if (remoteControlState === REMOTE_CONTROL_MENU_STATES.NOT_STARTED) {
|
||||
eventName = 'start';
|
||||
}
|
||||
|
||||
if (eventName) {
|
||||
sendAnalyticsEvent(
|
||||
`${REMOTE_VIDEO_MENU_REMOTE_CONTROL_}.${eventName}`,
|
||||
sendAnalytics(createRemoteVideoMenuButtonEvent(
|
||||
'remote.control.button',
|
||||
{
|
||||
value: 1,
|
||||
label: participantID
|
||||
}
|
||||
);
|
||||
enable,
|
||||
'participant_id': participantID
|
||||
}));
|
||||
}
|
||||
|
||||
if (onClick) {
|
||||
|
|
|
@ -4,7 +4,10 @@ import PropTypes from 'prop-types';
|
|||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { TOOLBAR_PROFILE_TOGGLED, sendAnalyticsEvent } from '../../analytics';
|
||||
import {
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import {
|
||||
getAvatarURL,
|
||||
getLocalParticipant
|
||||
|
@ -115,7 +118,9 @@ class ProfileButton extends Component<*> {
|
|||
*/
|
||||
_onClick() {
|
||||
if (!this.props._unclickable) {
|
||||
sendAnalyticsEvent(TOOLBAR_PROFILE_TOGGLED);
|
||||
// TODO: Include an 'enable' attribute, which specifies whether
|
||||
// the profile panel was opened or closed.
|
||||
sendAnalytics(createToolbarEvent('profile'));
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_PROFILE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ import { View } from 'react-native';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TOOLBAR_AUDIO_MUTED,
|
||||
TOOLBAR_AUDIO_UNMUTED,
|
||||
TOOLBAR_VIDEO_,
|
||||
sendAnalyticsEvent
|
||||
AUDIO_MUTE,
|
||||
VIDEO_MUTE,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import {
|
||||
isNarrowAspectRatio,
|
||||
|
@ -188,7 +188,11 @@ class Toolbox extends Component {
|
|||
_onToggleAudio() {
|
||||
const mute = !this.props._audioMuted;
|
||||
|
||||
sendAnalyticsEvent(mute ? TOOLBAR_AUDIO_MUTED : TOOLBAR_AUDIO_UNMUTED);
|
||||
sendAnalytics(createToolbarEvent(
|
||||
AUDIO_MUTE,
|
||||
{
|
||||
enable: mute
|
||||
}));
|
||||
|
||||
// The user sees the reality i.e. the state of base/tracks and intends
|
||||
// to change reality by tapping on the respective button i.e. the user
|
||||
|
@ -211,7 +215,11 @@ class Toolbox extends Component {
|
|||
_onToggleVideo() {
|
||||
const mute = !this.props._videoMuted;
|
||||
|
||||
sendAnalyticsEvent(`${TOOLBAR_VIDEO_}.${mute ? 'muted' : 'unmuted'}`);
|
||||
sendAnalytics(createToolbarEvent(
|
||||
VIDEO_MUTE,
|
||||
{
|
||||
enable: mute
|
||||
}));
|
||||
|
||||
// The user sees the reality i.e. the state of base/tracks and intends
|
||||
// to change reality by tapping on the respective button i.e. the user
|
||||
|
|
|
@ -3,29 +3,12 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
SHORTCUT_AUDIO_MUTE_TOGGLED,
|
||||
SHORTCUT_CHAT_TOGGLED,
|
||||
SHORTCUT_RAISE_HAND_CLICKED,
|
||||
SHORTCUT_SCREEN_TOGGLED,
|
||||
SHORTCUT_VIDEO_MUTE_TOGGLED,
|
||||
TOOLBAR_AUDIO_MUTED,
|
||||
TOOLBAR_AUDIO_UNMUTED,
|
||||
TOOLBAR_CHAT_TOGGLED,
|
||||
TOOLBAR_CONTACTS_TOGGLED,
|
||||
TOOLBAR_ETHERPACK_CLICKED,
|
||||
TOOLBAR_FILMSTRIP_ONLY_DEVICE_SELECTION_TOGGLED,
|
||||
TOOLBAR_FULLSCREEN_ENABLED,
|
||||
TOOLBAR_HANGUP,
|
||||
TOOLBAR_INVITE_CLICKED,
|
||||
TOOLBAR_RAISE_HAND_CLICKED,
|
||||
TOOLBAR_SCREEN_DISABLED,
|
||||
TOOLBAR_SCREEN_ENABLED,
|
||||
TOOLBAR_SETTINGS_TOGGLED,
|
||||
TOOLBAR_SHARED_VIDEO_CLICKED,
|
||||
TOOLBAR_SIP_DIALPAD_CLICKED,
|
||||
TOOLBAR_VIDEO_DISABLED,
|
||||
TOOLBAR_VIDEO_ENABLED,
|
||||
sendAnalyticsEvent
|
||||
ACTION_SHORTCUT_TRIGGERED as TRIGGERED,
|
||||
AUDIO_MUTE,
|
||||
VIDEO_MUTE,
|
||||
createShortcutEvent,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../analytics';
|
||||
import { ParticipantCounter } from '../contact-list';
|
||||
import { openDeviceSelectionDialog } from '../device-selection';
|
||||
|
@ -63,13 +46,18 @@ export default function getDefaultButtons() {
|
|||
isDisplayed: () => true,
|
||||
id: 'toolbar_button_camera',
|
||||
onClick() {
|
||||
// TODO: Why is this different from the code which handles
|
||||
// a keyboard shortcut?
|
||||
const newVideoMutedState = !APP.conference.isLocalVideoMuted();
|
||||
|
||||
if (newVideoMutedState) {
|
||||
sendAnalyticsEvent(TOOLBAR_VIDEO_ENABLED);
|
||||
} else {
|
||||
sendAnalyticsEvent(TOOLBAR_VIDEO_DISABLED);
|
||||
}
|
||||
// The 'enable' attribute in the event is set to true if the
|
||||
// button click triggered a mute action, and set to false if it
|
||||
// triggered an unmute action.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
VIDEO_MUTE,
|
||||
{
|
||||
enable: newVideoMutedState
|
||||
}));
|
||||
APP.UI.emitEvent(UIEvents.VIDEO_MUTED, newVideoMutedState);
|
||||
},
|
||||
popups: [
|
||||
|
@ -88,7 +76,13 @@ export default function getDefaultButtons() {
|
|||
return;
|
||||
}
|
||||
|
||||
sendAnalyticsEvent(SHORTCUT_VIDEO_MUTE_TOGGLED);
|
||||
// The 'enable' attribute in the event is set to true if the
|
||||
// shortcut triggered a mute action, and set to false if it
|
||||
// triggered an unmute action.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
VIDEO_MUTE,
|
||||
TRIGGERED,
|
||||
{ enable: !APP.conference.isLocalVideoMuted() }));
|
||||
APP.conference.toggleVideoMuted();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.videoMute',
|
||||
|
@ -105,13 +99,26 @@ export default function getDefaultButtons() {
|
|||
<span id = 'unreadMessages' /></span>,
|
||||
id: 'toolbar_button_chat',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_CHAT_TOGGLED);
|
||||
// The 'enable' attribute is set to true if the click resulted
|
||||
// in the chat panel being shown, and to false if it was hidden.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.chat',
|
||||
{
|
||||
enable: !APP.UI.Chat.isVisible()
|
||||
}));
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_CHAT);
|
||||
},
|
||||
shortcut: 'C',
|
||||
shortcutAttr: 'toggleChatPopover',
|
||||
shortcutFunc() {
|
||||
sendAnalyticsEvent(SHORTCUT_CHAT_TOGGLED);
|
||||
// The 'enable' attribute is set to true if the shortcut
|
||||
// resulted in the chat panel being shown, and to false if it
|
||||
// was hidden.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.chat',
|
||||
{
|
||||
enable: !APP.UI.Chat.isVisible()
|
||||
}));
|
||||
APP.UI.toggleChat();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.toggleChat',
|
||||
|
@ -128,7 +135,9 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_contact_list',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_CONTACTS_TOGGLED);
|
||||
// TODO: Include an 'enable' attribute which specifies whether
|
||||
// the contacts panel was shown or hidden.
|
||||
sendAnalytics(createToolbarEvent('contacts'));
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_CONTACT_LIST);
|
||||
},
|
||||
sideContainerId: 'contacts_container',
|
||||
|
@ -143,11 +152,14 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_desktopsharing',
|
||||
onClick() {
|
||||
if (APP.conference.isSharingScreen) {
|
||||
sendAnalyticsEvent(TOOLBAR_SCREEN_DISABLED);
|
||||
} else {
|
||||
sendAnalyticsEvent(TOOLBAR_SCREEN_ENABLED);
|
||||
}
|
||||
// TODO: Why is the button clicked handled differently that
|
||||
// a keyboard shortcut press (firing a TOGGLE_SCREENSHARING
|
||||
// event vs. directly calling toggleScreenSharing())?
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'screen.sharing',
|
||||
{
|
||||
enable: !APP.conference.isSharingScreen
|
||||
}));
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
|
||||
},
|
||||
popups: [
|
||||
|
@ -160,7 +172,13 @@ export default function getDefaultButtons() {
|
|||
shortcut: 'D',
|
||||
shortcutAttr: 'toggleDesktopSharingPopover',
|
||||
shortcutFunc() {
|
||||
sendAnalyticsEvent(SHORTCUT_SCREEN_TOGGLED);
|
||||
// The 'enable' attribute is set to true if pressing the
|
||||
// shortcut resulted in screen sharing being enabled, and false
|
||||
// if it resulted in screen sharing being disabled.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.screen.sharing',
|
||||
TRIGGERED,
|
||||
{ enable: !APP.conference.isSharingScreen }));
|
||||
|
||||
// eslint-disable-next-line no-empty-function
|
||||
APP.conference.toggleScreenSharing().catch(() => {});
|
||||
|
@ -180,8 +198,8 @@ export default function getDefaultButtons() {
|
|||
},
|
||||
id: 'toolbar_button_fodeviceselection',
|
||||
onClick(dispatch: Function) {
|
||||
sendAnalyticsEvent(
|
||||
TOOLBAR_FILMSTRIP_ONLY_DEVICE_SELECTION_TOGGLED);
|
||||
sendAnalytics(
|
||||
createToolbarEvent('filmstrip.only.device.selection'));
|
||||
|
||||
dispatch(openDeviceSelectionDialog());
|
||||
},
|
||||
|
@ -200,7 +218,7 @@ export default function getDefaultButtons() {
|
|||
hidden: true,
|
||||
id: 'toolbar_button_dialpad',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_SIP_DIALPAD_CLICKED);
|
||||
sendAnalytics(createToolbarEvent('dialpad'));
|
||||
},
|
||||
tooltipKey: 'toolbar.dialpad'
|
||||
},
|
||||
|
@ -214,7 +232,13 @@ export default function getDefaultButtons() {
|
|||
hidden: true,
|
||||
id: 'toolbar_button_etherpad',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_ETHERPACK_CLICKED);
|
||||
// The 'enable' attribute is set to true if the click resulted
|
||||
// in the etherpad panel being shown, or false it it was hidden.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.etherpad',
|
||||
{
|
||||
enable: !APP.UI.isEtherpadVisible()
|
||||
}));
|
||||
APP.UI.emitEvent(UIEvents.ETHERPAD_CLICKED);
|
||||
},
|
||||
tooltipKey: 'toolbar.etherpad'
|
||||
|
@ -228,7 +252,18 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_fullScreen',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_FULLSCREEN_ENABLED);
|
||||
// TODO: why is the fullscreen button handled differently than
|
||||
// the fullscreen keyboard shortcut (one results in a direct
|
||||
// call to toggleFullScreen, while the other fires an
|
||||
// UIEvents.TOGGLE_FULLSCREEN event)?
|
||||
|
||||
// The 'enable' attribute is set to true if the action resulted
|
||||
// in fullscreen mode being enabled.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'toggle.fullscreen',
|
||||
{
|
||||
enable: !APP.UI.isFullScreen()
|
||||
}));
|
||||
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_FULLSCREEN);
|
||||
},
|
||||
|
@ -236,7 +271,13 @@ export default function getDefaultButtons() {
|
|||
shortcutAttr: 'toggleFullscreenPopover',
|
||||
shortcutDescription: 'keyboardShortcuts.fullScreen',
|
||||
shortcutFunc() {
|
||||
sendAnalyticsEvent('shortcut.fullscreen.toggled');
|
||||
// The 'enable' attribute is set to true if the action resulted
|
||||
// in fullscreen mode being enabled.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.fullscreen',
|
||||
{
|
||||
enable: !APP.UI.isFullScreen()
|
||||
}));
|
||||
APP.UI.toggleFullScreen();
|
||||
},
|
||||
tooltipKey: 'toolbar.fullscreen'
|
||||
|
@ -252,7 +293,7 @@ export default function getDefaultButtons() {
|
|||
isDisplayed: () => true,
|
||||
id: 'toolbar_button_hangup',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_HANGUP);
|
||||
sendAnalytics(createToolbarEvent('hangup'));
|
||||
APP.UI.emitEvent(UIEvents.HANGUP);
|
||||
},
|
||||
tooltipKey: 'toolbar.hangup'
|
||||
|
@ -275,7 +316,7 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_link',
|
||||
onClick(dispatch: Function) {
|
||||
sendAnalyticsEvent(TOOLBAR_INVITE_CLICKED);
|
||||
sendAnalytics(createToolbarEvent('invite'));
|
||||
|
||||
dispatch(openInviteDialog());
|
||||
},
|
||||
|
@ -293,6 +334,13 @@ export default function getDefaultButtons() {
|
|||
onClick() {
|
||||
const sharedVideoManager = APP.UI.getSharedVideoManager();
|
||||
|
||||
// TODO: Clicking the mute button and pressing the mute shortcut
|
||||
// could be handled in a uniform manner. The code below checks
|
||||
// the mute status and fires the appropriate event (MUTED or
|
||||
// UNMUTED), while the code which handles the keyboard shortcut
|
||||
// calls toggleAudioMuted(). Also strangely the the user is
|
||||
// only warned if they click the button (and not if they use
|
||||
// the shortcut).
|
||||
if (APP.conference.isLocalAudioMuted()) {
|
||||
// If there's a shared video with the volume "on" and we
|
||||
// aren't the video owner, we warn the user
|
||||
|
@ -303,11 +351,15 @@ export default function getDefaultButtons() {
|
|||
APP.UI.showCustomToolbarPopup(
|
||||
'microphone', 'unableToUnmutePopup', true, 5000);
|
||||
} else {
|
||||
sendAnalyticsEvent(TOOLBAR_AUDIO_UNMUTED);
|
||||
sendAnalytics(createToolbarEvent(
|
||||
AUDIO_MUTE,
|
||||
{ enable: false }));
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, false, true);
|
||||
}
|
||||
} else {
|
||||
sendAnalyticsEvent(TOOLBAR_AUDIO_MUTED);
|
||||
sendAnalytics(createToolbarEvent(
|
||||
AUDIO_MUTE,
|
||||
{ enable: true }));
|
||||
APP.UI.emitEvent(UIEvents.AUDIO_MUTED, true, true);
|
||||
}
|
||||
},
|
||||
|
@ -328,7 +380,13 @@ export default function getDefaultButtons() {
|
|||
shortcut: 'M',
|
||||
shortcutAttr: 'mutePopover',
|
||||
shortcutFunc() {
|
||||
sendAnalyticsEvent(SHORTCUT_AUDIO_MUTE_TOGGLED);
|
||||
// The 'enable' attribute in the event is set to true if the
|
||||
// shortcut triggered a mute action, and set to false if it
|
||||
// triggered an unmute action.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
AUDIO_MUTE,
|
||||
TRIGGERED,
|
||||
{ enable: !APP.conference.isLocalAudioMuted() }));
|
||||
APP.conference.toggleAudioMuted();
|
||||
},
|
||||
shortcutDescription: 'keyboardShortcuts.mute',
|
||||
|
@ -351,14 +409,27 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_raisehand',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_RAISE_HAND_CLICKED);
|
||||
// TODO: reduce duplication with shortcutFunc below.
|
||||
|
||||
// The 'enable' attribute is set to true if the pressing of the
|
||||
// shortcut resulted in the hand being raised, and to false
|
||||
// if it resulted in the hand being 'lowered'.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'raise.hand',
|
||||
{ enable: !APP.conference.isHandRaised }));
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
shortcut: 'R',
|
||||
shortcutAttr: 'raiseHandPopover',
|
||||
shortcutDescription: 'keyboardShortcuts.raiseHand',
|
||||
shortcutFunc() {
|
||||
sendAnalyticsEvent(SHORTCUT_RAISE_HAND_CLICKED);
|
||||
// The 'enable' attribute is set to true if the pressing of the
|
||||
// shortcut resulted in the hand being raised, and to false
|
||||
// if it resulted in the hand being 'lowered'.
|
||||
sendAnalytics(createShortcutEvent(
|
||||
'toggle.raise.hand',
|
||||
TRIGGERED,
|
||||
{ enable: !APP.conference.isHandRaised }));
|
||||
APP.conference.maybeToggleRaisedHand();
|
||||
},
|
||||
tooltipKey: 'toolbar.raiseHand'
|
||||
|
@ -386,7 +457,9 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_settings',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_SETTINGS_TOGGLED);
|
||||
// TODO: Include an 'enable' attribute which specifies whether
|
||||
// the settings panel was shown or hidden.
|
||||
sendAnalytics(createToolbarEvent('settings'));
|
||||
APP.UI.emitEvent(UIEvents.TOGGLE_SETTINGS);
|
||||
},
|
||||
sideContainerId: 'settings_container',
|
||||
|
@ -401,7 +474,15 @@ export default function getDefaultButtons() {
|
|||
enabled: true,
|
||||
id: 'toolbar_button_sharedvideo',
|
||||
onClick() {
|
||||
sendAnalyticsEvent(TOOLBAR_SHARED_VIDEO_CLICKED);
|
||||
// The 'enable' attribute is set to true if the click resulted
|
||||
// in the "start sharing video" dialog being shown, and false
|
||||
// if it resulted in the "stop sharing video" dialog being
|
||||
// shown.
|
||||
sendAnalytics(createToolbarEvent(
|
||||
'shared.video.toggled',
|
||||
{
|
||||
enable: !APP.UI.isSharedVideoShown()
|
||||
}));
|
||||
APP.UI.emitEvent(UIEvents.SHARED_VIDEO_CLICKED);
|
||||
},
|
||||
popups: [
|
||||
|
|
|
@ -4,16 +4,13 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import {
|
||||
TOOLBAR_AUDIO_ONLY_ENABLED,
|
||||
TOOLBAR_VIDEO_QUALITY_HIGH,
|
||||
TOOLBAR_VIDEO_QUALITY_LOW,
|
||||
TOOLBAR_VIDEO_QUALITY_STANDARD,
|
||||
sendAnalyticsEvent
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import {
|
||||
VIDEO_QUALITY_LEVELS,
|
||||
setAudioOnly,
|
||||
setReceiveVideoQuality,
|
||||
VIDEO_QUALITY_LEVELS
|
||||
setReceiveVideoQuality
|
||||
} from '../../base/conference';
|
||||
import { translate } from '../../base/i18n';
|
||||
import JitsiMeetJS from '../../base/lib-jitsi-meet';
|
||||
|
@ -26,6 +23,22 @@ const {
|
|||
LOW
|
||||
} = VIDEO_QUALITY_LEVELS;
|
||||
|
||||
/**
|
||||
* Creates an analytics event for a press of one of the buttons in the video
|
||||
* quality dialog.
|
||||
*
|
||||
* @param {string} quality - The quality which was selected.
|
||||
* @returns {Object} The event in a format suitable for sending via
|
||||
* sendAnalytics.
|
||||
*/
|
||||
const createEvent = function(quality) {
|
||||
return createToolbarEvent(
|
||||
'video.quality',
|
||||
{
|
||||
quality
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Implements a React {@link Component} which displays a dialog with a slider
|
||||
* for selecting a new receive video quality.
|
||||
|
@ -255,12 +268,13 @@ class VideoQualityDialog extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
_enableAudioOnly() {
|
||||
sendAnalyticsEvent(TOOLBAR_AUDIO_ONLY_ENABLED);
|
||||
sendAnalytics(createEvent('audio.only'));
|
||||
logger.log('Video quality: audio only enabled');
|
||||
this.props.dispatch(setAudioOnly(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the action of the high definition video being selected.
|
||||
* Dispatches an action to receive high quality video from remote
|
||||
* participants.
|
||||
*
|
||||
|
@ -268,7 +282,7 @@ class VideoQualityDialog extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
_enableHighDefinition() {
|
||||
sendAnalyticsEvent(TOOLBAR_VIDEO_QUALITY_HIGH);
|
||||
sendAnalytics(createEvent('high'));
|
||||
logger.log('Video quality: high enabled');
|
||||
this.props.dispatch(setReceiveVideoQuality(HIGH));
|
||||
}
|
||||
|
@ -281,7 +295,7 @@ class VideoQualityDialog extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
_enableLowDefinition() {
|
||||
sendAnalyticsEvent(TOOLBAR_VIDEO_QUALITY_LOW);
|
||||
sendAnalytics(createEvent('low'));
|
||||
logger.log('Video quality: low enabled');
|
||||
this.props.dispatch(setReceiveVideoQuality(LOW));
|
||||
}
|
||||
|
@ -294,7 +308,7 @@ class VideoQualityDialog extends Component {
|
|||
* @returns {void}
|
||||
*/
|
||||
_enableStandardDefinition() {
|
||||
sendAnalyticsEvent(TOOLBAR_VIDEO_QUALITY_STANDARD);
|
||||
sendAnalytics(createEvent('standard'));
|
||||
logger.log('Video quality: standard enabled');
|
||||
this.props.dispatch(setReceiveVideoQuality(STANDARD));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue