Procházet zdrojové kódy

feat(local-recordings) drop old "local recordings" implementation

It's about to become very confusing, since we are going to add actual
local recordings with video.

This feature was never fully finalizeed since it required manual
processing of the files, as they were not uploaded anywhere.

In addition, unless one opens the local audio device without any audio
processing first, any tracks opened later will have audio proceessing
turned on, something not desirable for the scenario this feature was
designed for in the first place: podcasts.

This feature will likely come back as a JaaS demo / MVP where the local
recording is made outside of the Jitsi Meet iframe.
master
Saúl Ibarra Corretgé před 3 roky
rodič
revize
9dd44fc48e
39 změnil soubory, kde provedl 5 přidání a 3444 odebrání
  1. 2
    11
      Makefile
  2. 1
    1
      config.js
  3. 0
    1
      css/main.scss
  4. 0
    92
      css/modals/local-recording/_local-recording.scss
  5. 0
    29
      package-lock.json
  6. 0
    1
      package.json
  7. 0
    1
      react/features/app/middlewares.web.js
  8. 0
    1
      react/features/app/reducers.web.js
  9. 1
    1
      react/features/base/config/reducer.js
  10. 1
    1
      react/features/conference/components/constants.js
  11. 0
    5
      react/features/conference/components/web/ConferenceInfo.js
  12. 0
    32
      react/features/local-recording/actionTypes.ts
  13. 0
    59
      react/features/local-recording/actions.js
  14. 0
    0
      react/features/local-recording/components/LocalRecordingButton.native.js
  15. 0
    46
      react/features/local-recording/components/LocalRecordingButton.web.js
  16. 0
    407
      react/features/local-recording/components/LocalRecordingInfoDialog.js
  17. 0
    0
      react/features/local-recording/components/LocalRecordingLabel.native.js
  18. 0
    82
      react/features/local-recording/components/LocalRecordingLabel.web.js
  19. 0
    5
      react/features/local-recording/components/index.js
  20. 0
    687
      react/features/local-recording/controller/RecordingController.js
  21. 0
    1
      react/features/local-recording/controller/index.js
  22. 0
    4
      react/features/local-recording/index.js
  23. 0
    5
      react/features/local-recording/logger.js
  24. 0
    97
      react/features/local-recording/middleware.js
  25. 0
    129
      react/features/local-recording/recording/AbstractAudioContextAdapter.js
  26. 0
    146
      react/features/local-recording/recording/OggAdapter.js
  27. 0
    85
      react/features/local-recording/recording/RecordingAdapter.js
  28. 0
    20
      react/features/local-recording/recording/Utils.js
  29. 0
    290
      react/features/local-recording/recording/WavAdapter.js
  30. 0
    262
      react/features/local-recording/recording/flac/FlacAdapter.js
  31. 0
    399
      react/features/local-recording/recording/flac/flacEncodeWorker.js
  32. 0
    1
      react/features/local-recording/recording/flac/index.js
  33. 0
    44
      react/features/local-recording/recording/flac/messageTypes.js
  34. 0
    5
      react/features/local-recording/recording/index.js
  35. 0
    36
      react/features/local-recording/reducer.js
  36. 0
    439
      react/features/local-recording/session/SessionManager.js
  37. 0
    1
      react/features/local-recording/session/index.js
  38. 0
    8
      react/features/toolbox/components/web/Toolbox.js
  39. 0
    10
      webpack.config.js

+ 2
- 11
Makefile Zobrazit soubor

@@ -2,7 +2,6 @@ BUILD_DIR = build
2 2
 CLEANCSS = ./node_modules/.bin/cleancss
3 3
 DEPLOY_DIR = libs
4 4
 LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet
5
-LIBFLAC_DIR = node_modules/libflacjs/dist/min
6 5
 OLM_DIR = node_modules/@matrix-org/olm
7 6
 TF_WASM_DIR = node_modules/@tensorflow/tfjs-backend-wasm/dist/
8 7
 RNNOISE_WASM_DIR = node_modules/rnnoise-wasm/dist
@@ -30,7 +29,7 @@ clean:
30 29
 	rm -fr $(BUILD_DIR)
31 30
 
32 31
 .NOTPARALLEL:
33
-deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-tf-wasm deploy-css deploy-local deploy-face-landmarks
32
+deploy: deploy-init deploy-appbundle deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-css deploy-local deploy-face-landmarks
34 33
 
35 34
 deploy-init:
36 35
 	rm -fr $(DEPLOY_DIR)
@@ -44,8 +43,6 @@ deploy-appbundle:
44 43
 		$(BUILD_DIR)/do_external_connect.min.js.map \
45 44
 		$(BUILD_DIR)/external_api.min.js \
46 45
 		$(BUILD_DIR)/external_api.min.js.map \
47
-		$(BUILD_DIR)/flacEncodeWorker.min.js \
48
-		$(BUILD_DIR)/flacEncodeWorker.min.js.map \
49 46
 		$(BUILD_DIR)/dial_in_info_bundle.min.js \
50 47
 		$(BUILD_DIR)/dial_in_info_bundle.min.js.map \
51 48
 		$(BUILD_DIR)/alwaysontop.min.js \
@@ -70,12 +67,6 @@ deploy-lib-jitsi-meet:
70 67
 		$(LIBJITSIMEET_DIR)/modules/browser/capabilities.json \
71 68
 		$(DEPLOY_DIR)
72 69
 
73
-deploy-libflac:
74
-	cp \
75
-		$(LIBFLAC_DIR)/libflac4-1.3.2.min.js \
76
-		$(LIBFLAC_DIR)/libflac4-1.3.2.min.js.mem \
77
-		$(DEPLOY_DIR)
78
-
79 70
 deploy-olm:
80 71
 	cp \
81 72
 		$(OLM_DIR)/olm.wasm \
@@ -118,7 +109,7 @@ deploy-local:
118 109
 	([ ! -x deploy-local.sh ] || ./deploy-local.sh)
119 110
 
120 111
 .NOTPARALLEL:
121
-dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-libflac deploy-olm deploy-tf-wasm deploy-face-landmarks
112
+dev: deploy-init deploy-css deploy-rnnoise-binary deploy-tflite deploy-meet-models deploy-lib-jitsi-meet deploy-olm deploy-tf-wasm deploy-face-landmarks
122 113
 	$(WEBPACK_DEV_SERVER)
123 114
 
124 115
 source-package:

+ 1
- 1
config.js Zobrazit soubor

@@ -1143,7 +1143,7 @@ var config = {
1143 1143
     // If a label's id is not in any of the 2 arrays, it will not be visible at all on the header.
1144 1144
     // conferenceInfo: {
1145 1145
     //     // those labels will not be hidden in tandem with the toolbox.
1146
-    //     alwaysVisible: ['recording', 'local-recording', 'raised-hands-count'],
1146
+    //     alwaysVisible: ['recording', 'raised-hands-count'],
1147 1147
     //     // those labels will be auto-hidden in tandem with the toolbox buttons.
1148 1148
     //     autoHide: [
1149 1149
     //         'subject',

+ 0
- 1
css/main.scss Zobrazit soubor

@@ -40,7 +40,6 @@ $flagsImagePath: "../images/";
40 40
 @import 'modals/invite/info';
41 41
 @import 'modals/screen-share/share-audio';
42 42
 @import 'modals/screen-share/share-screen-warning';
43
-@import 'modals/local-recording/local-recording';
44 43
 @import 'videolayout_default';
45 44
 @import 'notice';
46 45
 @import 'subject';

+ 0
- 92
css/modals/local-recording/_local-recording.scss Zobrazit soubor

@@ -1,92 +0,0 @@
1
-.localrec-participant-stats {
2
-    list-style: none;
3
-    padding: 0;
4
-    width: 100%;
5
-    font-weight: 500;
6
-
7
-    .localrec-participant-stats-item__status-dot {
8
-        position: relative;
9
-        display: block;
10
-        width: 9px;
11
-        height: 9px;
12
-        border-radius: 50%;
13
-        margin: 0 auto;
14
-
15
-        &.status-on {
16
-            background: green;
17
-        }
18
-
19
-        &.status-off {
20
-            background: gray;
21
-        }
22
-
23
-        &.status-unknown {
24
-            background: darkgoldenrod;
25
-        }
26
-
27
-        &.status-error {
28
-            background: darkred;
29
-        }
30
-    }
31
-
32
-    .localrec-participant-stats-item__status,
33
-    .localrec-participant-stats-item__name,
34
-    .localrec-participant-stats-item__sessionid {
35
-        display: inline-block;
36
-        margin: 5px 0;
37
-        vertical-align: middle;
38
-    }
39
-    .localrec-participant-stats-item__status {
40
-        width: 5%;
41
-    }
42
-    .localrec-participant-stats-item__name {
43
-        width: 40%;
44
-    }
45
-    .localrec-participant-stats-item__sessionid {
46
-        width: 55%;
47
-    }
48
-
49
-    .localrec-participant-stats-item__name,
50
-    .localrec-participant-stats-item__sessionid {
51
-        overflow: hidden;
52
-        text-overflow: ellipsis;
53
-        white-space: nowrap;
54
-    }
55
-}
56
-
57
-.localrec-control-info-label {
58
-    font-weight: bold;
59
-}
60
-
61
-.localrec-control-info-label:after {
62
-    content: ' ';
63
-}
64
-
65
-.localrec-control-action-link {
66
-    display: inline-block;
67
-    line-height: 1.5em;
68
-
69
-    a {
70
-        cursor: pointer;
71
-        vertical-align: middle;
72
-    }
73
-}
74
-
75
-.localrec-control-action-link:before {
76
-    color: $linkFontColor;
77
-    content: '\2022';
78
-    font-size: 1.5em;
79
-    padding: 0 10px;
80
-    vertical-align: middle;
81
-}
82
-
83
-.localrec-control-action-link:first-child:before {
84
-    content: '';
85
-    padding: 0;
86
-}
87
-
88
-.localrec-control-action-links {
89
-    font-weight: bold;
90
-    margin-top: 10px;
91
-    white-space: nowrap;
92
-}

+ 0
- 29
package-lock.json Zobrazit soubor

@@ -73,7 +73,6 @@
73 73
         "js-md5": "0.6.1",
74 74
         "jwt-decode": "2.2.0",
75 75
         "lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1430.0.0+ccf9ebed/lib-jitsi-meet.tgz",
76
-        "libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
77 76
         "lodash": "4.17.21",
78 77
         "moment": "2.29.2",
79 78
         "moment-duration-format": "2.2.2",
@@ -12155,29 +12154,6 @@
12155 12154
         "uuid": "dist/bin/uuid"
12156 12155
       }
12157 12156
     },
12158
-    "node_modules/libflacjs": {
12159
-      "version": "4.0.0",
12160
-      "resolved": "git+https://git@github.com/mmig/libflac.js.git#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
12161
-      "integrity": "sha512-7cscxyqMkeUa5PpHOqhIkXgyrmPqxCzYobtXnrnwXFkY5+tvRjwsZqQQ52Z9K4AebDzpNaApK+NVn+gK4CwWUw==",
12162
-      "license": "MIT",
12163
-      "bin": {
12164
-        "libflac4-1.3.2.dev.js": "dist/dev/libflac4-1.3.2.dev.js",
12165
-        "libflac4-1.3.2.dev.js.map": "dist/dev/libflac4-1.3.2.dev.js.map",
12166
-        "libflac4-1.3.2.dev.wasm.js": "dist/dev/libflac4-1.3.2.dev.wasm.js",
12167
-        "libflac4-1.3.2.dev.wasm.wasm": "dist/dev/libflac4-1.3.2.dev.wasm.wasm",
12168
-        "libflac4-1.3.2.dev.wasm.wasm.map": "dist/dev/libflac4-1.3.2.dev.wasm.wasm.map",
12169
-        "libflac4-1.3.2.dev.wasm.wast": "dist/dev/libflac4-1.3.2.dev.wasm.wast",
12170
-        "libflac4-1.3.2.js": "dist/libflac4-1.3.2.js",
12171
-        "libflac4-1.3.2.min.js": "dist/min/libflac4-1.3.2.min.js",
12172
-        "libflac4-1.3.2.min.js.mem": "dist/min/libflac4-1.3.2.min.js.mem",
12173
-        "libflac4-1.3.2.min.js.symbols": "dist/min/libflac4-1.3.2.min.js.symbols",
12174
-        "libflac4-1.3.2.min.wasm.js": "dist/min/libflac4-1.3.2.min.wasm.js",
12175
-        "libflac4-1.3.2.min.wasm.js.symbols": "dist/min/libflac4-1.3.2.min.wasm.js.symbols",
12176
-        "libflac4-1.3.2.min.wasm.wasm": "dist/min/libflac4-1.3.2.min.wasm.wasm",
12177
-        "libflac4-1.3.2.wasm.js": "dist/libflac4-1.3.2.wasm.js",
12178
-        "libflac4-1.3.2.wasm.wasm": "dist/libflac4-1.3.2.wasm.wasm"
12179
-      }
12180
-    },
12181 12157
     "node_modules/lines-and-columns": {
12182 12158
       "version": "1.2.4",
12183 12159
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
@@ -29312,11 +29288,6 @@
29312 29288
         }
29313 29289
       }
29314 29290
     },
29315
-    "libflacjs": {
29316
-      "version": "git+https://git@github.com/mmig/libflac.js.git#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
29317
-      "integrity": "sha512-7cscxyqMkeUa5PpHOqhIkXgyrmPqxCzYobtXnrnwXFkY5+tvRjwsZqQQ52Z9K4AebDzpNaApK+NVn+gK4CwWUw==",
29318
-      "from": "libflacjs@https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d"
29319
-    },
29320 29291
     "lines-and-columns": {
29321 29292
       "version": "1.2.4",
29322 29293
       "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",

+ 0
- 1
package.json Zobrazit soubor

@@ -78,7 +78,6 @@
78 78
     "js-md5": "0.6.1",
79 79
     "jwt-decode": "2.2.0",
80 80
     "lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1430.0.0+ccf9ebed/lib-jitsi-meet.tgz",
81
-    "libflacjs": "https://git@github.com/mmig/libflac.js#93d37e7f811f01cf7d8b6a603e38bd3c3810907d",
82 81
     "lodash": "4.17.21",
83 82
     "moment": "2.29.2",
84 83
     "moment-duration-format": "2.2.2",

+ 0
- 1
react/features/app/middlewares.web.js Zobrazit soubor

@@ -7,7 +7,6 @@ import '../dynamic-branding/middleware';
7 7
 import '../e2ee/middleware';
8 8
 import '../external-api/middleware';
9 9
 import '../keyboard-shortcuts/middleware';
10
-import '../local-recording/middleware';
11 10
 import '../no-audio-signal/middleware';
12 11
 import '../notifications/middleware';
13 12
 import '../noise-detection/middleware';

+ 0
- 1
react/features/app/reducers.web.js Zobrazit soubor

@@ -4,7 +4,6 @@ import '../base/devices/reducer';
4 4
 import '../e2ee/reducer';
5 5
 import '../face-landmarks/reducer';
6 6
 import '../feedback/reducer';
7
-import '../local-recording/reducer';
8 7
 import '../no-audio-signal/reducer';
9 8
 import '../noise-detection/reducer';
10 9
 import '../participants-pane/reducer';

+ 1
- 1
react/features/base/config/reducer.js Zobrazit soubor

@@ -65,7 +65,7 @@ const CONFERENCE_HEADER_MAPPING = {
65 65
     hideConferenceTimer: [ 'conference-timer' ],
66 66
     hideConferenceSubject: [ 'subject' ],
67 67
     hideParticipantsStats: [ 'participants-count' ],
68
-    hideRecordingLabel: [ 'recording', 'local-recording' ]
68
+    hideRecordingLabel: [ 'recording' ]
69 69
 };
70 70
 
71 71
 ReducerRegistry.register('features/base/config', (state = _getInitialState(), action) => {

+ 1
- 1
react/features/conference/components/constants.js Zobrazit soubor

@@ -1,5 +1,5 @@
1 1
 export const CONFERENCE_INFO = {
2
-    alwaysVisible: [ 'recording', 'local-recording', 'raised-hands-count' ],
2
+    alwaysVisible: [ 'recording', 'raised-hands-count' ],
3 3
     autoHide: [
4 4
         'highlight-moment',
5 5
         'subject',

+ 0
- 5
react/features/conference/components/web/ConferenceInfo.js Zobrazit soubor

@@ -7,7 +7,6 @@ import React, { Component } from 'react';
7 7
 import { JitsiRecordingConstants } from '../../../base/lib-jitsi-meet';
8 8
 import { connect } from '../../../base/redux';
9 9
 import { E2EELabel } from '../../../e2ee';
10
-import { LocalRecordingLabel } from '../../../local-recording';
11 10
 import { RecordingLabel } from '../../../recording';
12 11
 import HighlightButton from '../../../recording/components/Recording/web/HighlightButton';
13 12
 import { isToolboxVisible } from '../../../toolbox/functions.web';
@@ -68,10 +67,6 @@ const COMPONENTS = [
68 67
         ),
69 68
         id: 'recording'
70 69
     },
71
-    {
72
-        Component: LocalRecordingLabel,
73
-        id: 'local-recording'
74
-    },
75 70
     {
76 71
         Component: RaisedHandsCountLabel,
77 72
         id: 'raised-hands-count'

+ 0
- 32
react/features/local-recording/actionTypes.ts Zobrazit soubor

@@ -1,32 +0,0 @@
1
-/**
2
- * Action to signal that the local client has started to perform recording,
3
- * (as in: {@code RecordingAdapter} is actively collecting audio data).
4
- *
5
- * {
6
- *     type: LOCAL_RECORDING_ENGAGED,
7
- *     recordingEngagedAt: Date
8
- * }
9
- */
10
-export const LOCAL_RECORDING_ENGAGED = 'LOCAL_RECORDING_ENGAGED';
11
-
12
-/**
13
- * Action to signal that the local client has stopped recording,
14
- * (as in: {@code RecordingAdapter} is no longer collecting audio data).
15
- *
16
- * {
17
- *     type: LOCAL_RECORDING_UNENGAGED
18
- * }
19
- */
20
-export const LOCAL_RECORDING_UNENGAGED = 'LOCAL_RECORDING_UNENGAGED';
21
-
22
-/**
23
- * Action to update {@code LocalRecordingInfoDialog} with stats from all
24
- * clients.
25
- *
26
- * {
27
- *     type: LOCAL_RECORDING_STATS_UPDATE,
28
- *     stats: Object
29
- * }
30
- */
31
-export const LOCAL_RECORDING_STATS_UPDATE
32
-    = 'LOCAL_RECORDING_STATS_UPDATE';

+ 0
- 59
react/features/local-recording/actions.js Zobrazit soubor

@@ -1,59 +0,0 @@
1
-/* @flow */
2
-
3
-import {
4
-    LOCAL_RECORDING_ENGAGED,
5
-    LOCAL_RECORDING_UNENGAGED,
6
-    LOCAL_RECORDING_STATS_UPDATE
7
-} from './actionTypes';
8
-
9
-// The following two actions signal state changes in local recording engagement.
10
-// In other words, the events of the local WebWorker / MediaRecorder starting to
11
-// record and finishing recording.
12
-// Note that this is not the event fired when the users tries to start the
13
-// recording in the UI.
14
-
15
-/**
16
- * Signals that local recording has been engaged.
17
- *
18
- * @param {Date} startTime - Time when the recording is engaged.
19
- * @returns {{
20
- *     type: LOCAL_RECORDING_ENGAGED,
21
- *     recordingEngagedAt: Date
22
- * }}
23
- */
24
-export function localRecordingEngaged(startTime: Date) {
25
-    return {
26
-        type: LOCAL_RECORDING_ENGAGED,
27
-        recordingEngagedAt: startTime
28
-    };
29
-}
30
-
31
-/**
32
- * Signals that local recording has finished.
33
- *
34
- * @returns {{
35
- *     type: LOCAL_RECORDING_UNENGAGED
36
- * }}
37
- */
38
-export function localRecordingUnengaged() {
39
-    return {
40
-        type: LOCAL_RECORDING_UNENGAGED
41
-    };
42
-}
43
-
44
-/**
45
- * Updates the the local recording stats from each client,
46
- * to be displayed on {@code LocalRecordingInfoDialog}.
47
- *
48
- * @param {*} stats - The stats object.
49
- * @returns {{
50
- *     type: LOCAL_RECORDING_STATS_UPDATE,
51
- *     stats: Object
52
- * }}
53
- */
54
-export function statsUpdate(stats: Object) {
55
-    return {
56
-        type: LOCAL_RECORDING_STATS_UPDATE,
57
-        stats
58
-    };
59
-}

+ 0
- 0
react/features/local-recording/components/LocalRecordingButton.native.js Zobrazit soubor


+ 0
- 46
react/features/local-recording/components/LocalRecordingButton.web.js Zobrazit soubor

@@ -1,46 +0,0 @@
1
-// @flow
2
-
3
-import { createToolbarEvent, sendAnalytics } from '../../analytics';
4
-import { openDialog } from '../../base/dialog';
5
-import { translate } from '../../base/i18n';
6
-import { IconRec } from '../../base/icons';
7
-import { connect } from '../../base/redux';
8
-import { AbstractButton, type AbstractButtonProps } from '../../base/toolbox/components';
9
-
10
-import LocalRecordingInfoDialog from './LocalRecordingInfoDialog';
11
-
12
-/**
13
- * The type of the React {@code Component} props of {@link LocalRecording}.
14
- */
15
-type Props = AbstractButtonProps & {
16
-
17
-    /**
18
-     * The redux {@code dispatch} function.
19
-     */
20
-    dispatch: Function
21
-};
22
-
23
-/**
24
- * Implementation of a button for opening local recording dialog.
25
- */
26
-class LocalRecording extends AbstractButton<Props, *> {
27
-    accessibilityLabel = 'toolbar.accessibilityLabel.localRecording';
28
-    icon = IconRec;
29
-    label = 'localRecording.dialogTitle';
30
-    tooltip = 'localRecording.dialogTitle';
31
-
32
-    /**
33
-     * Handles clicking / pressing the button, and opens the appropriate dialog.
34
-     *
35
-     * @protected
36
-     * @returns {void}
37
-     */
38
-    _handleClick() {
39
-        const { dispatch } = this.props;
40
-
41
-        sendAnalytics(createToolbarEvent('local.recording'));
42
-        dispatch(openDialog(LocalRecordingInfoDialog));
43
-    }
44
-}
45
-
46
-export default translate(connect()(LocalRecording));

+ 0
- 407
react/features/local-recording/components/LocalRecordingInfoDialog.js Zobrazit soubor

@@ -1,407 +0,0 @@
1
-// @flow
2
-
3
-import moment from 'moment';
4
-import React, { Component } from 'react';
5
-import type { Dispatch } from 'redux';
6
-
7
-import { Dialog } from '../../base/dialog';
8
-import { translate } from '../../base/i18n';
9
-import {
10
-    PARTICIPANT_ROLE,
11
-    getLocalParticipant
12
-} from '../../base/participants';
13
-import { connect } from '../../base/redux';
14
-import { statsUpdate } from '../actions';
15
-import { recordingController } from '../controller';
16
-
17
-
18
-/**
19
- * The type of the React {@code Component} props of
20
- * {@link LocalRecordingInfoDialog}.
21
- */
22
-type Props = {
23
-
24
-    /**
25
-     * Redux store dispatch function.
26
-     */
27
-    dispatch: Dispatch<any>,
28
-
29
-    /**
30
-     * Current encoding format.
31
-     */
32
-    encodingFormat: string,
33
-
34
-    /**
35
-     * Whether the local user is the moderator.
36
-     */
37
-    isModerator: boolean,
38
-
39
-    /**
40
-     * Whether local recording is engaged.
41
-     */
42
-    isEngaged: boolean,
43
-
44
-    /**
45
-     * The start time of the current local recording session.
46
-     * Used to calculate the duration of recording.
47
-     */
48
-    recordingEngagedAt: Date,
49
-
50
-    /**
51
-     * Stats of all the participant.
52
-     */
53
-    stats: Object,
54
-
55
-    /**
56
-     * Invoked to obtain translated strings.
57
-     */
58
-    t: Function
59
-}
60
-
61
-/**
62
- * The type of the React {@code Component} state of
63
- * {@link LocalRecordingInfoDialog}.
64
- */
65
-type State = {
66
-
67
-    /**
68
-     * The recording duration string to be displayed on the UI.
69
-     */
70
-    durationString: string
71
-}
72
-
73
-/**
74
- * A React Component with the contents for a dialog that shows information about
75
- * local recording. For users with moderator rights, this is also the "control
76
- * panel" for starting/stopping local recording on all clients.
77
- *
78
- * @augments Component
79
- */
80
-class LocalRecordingInfoDialog extends Component<Props, State> {
81
-
82
-    /**
83
-     * Saves a handle to the timer for UI updates,
84
-     * so that it can be cancelled when the component unmounts.
85
-     */
86
-    _timer: ?IntervalID;
87
-
88
-    /**
89
-     * Initializes a new {@code LocalRecordingInfoDialog} instance.
90
-     *
91
-     * @param {Props} props - The React {@code Component} props to initialize
92
-     * the new {@code LocalRecordingInfoDialog} instance with.
93
-     */
94
-    constructor(props: Props) {
95
-        super(props);
96
-        this.state = {
97
-            durationString: ''
98
-        };
99
-    }
100
-
101
-    /**
102
-     * Implements React's {@link Component#componentDidMount()}.
103
-     *
104
-     * @returns {void}
105
-     */
106
-    componentDidMount() {
107
-        this._timer = setInterval(
108
-            () => {
109
-                this.setState((_prevState, props) => {
110
-                    const nowTime = new Date();
111
-
112
-                    return {
113
-                        durationString: this._getDuration(nowTime,
114
-                            props.recordingEngagedAt)
115
-                    };
116
-                });
117
-                try {
118
-                    this.props.dispatch(
119
-                        statsUpdate(recordingController
120
-                            .getParticipantsStats()));
121
-                } catch (e) {
122
-                    // do nothing
123
-                }
124
-            },
125
-            1000
126
-        );
127
-    }
128
-
129
-    /**
130
-     * Implements React's {@link Component#componentWillUnmount()}.
131
-     *
132
-     * @returns {void}
133
-     */
134
-    componentWillUnmount() {
135
-        if (this._timer) {
136
-            clearInterval(this._timer);
137
-            this._timer = null;
138
-        }
139
-    }
140
-
141
-    /**
142
-     * Implements React's {@link Component#render()}.
143
-     *
144
-     * @inheritdoc
145
-     * @returns {ReactElement}
146
-     */
147
-    render() {
148
-        const { isModerator, t } = this.props;
149
-
150
-        return (
151
-            <Dialog
152
-                cancelKey = { 'dialog.close' }
153
-                submitDisabled = { true }
154
-                titleKey = 'localRecording.dialogTitle'>
155
-                <div className = 'localrec-control'>
156
-                    <span className = 'localrec-control-info-label'>
157
-                        {`${t('localRecording.moderator')}:`}
158
-                    </span>
159
-                    <span className = 'info-value'>
160
-                        { isModerator
161
-                            ? t('localRecording.yes')
162
-                            : t('localRecording.no') }
163
-                    </span>
164
-                </div>
165
-                { this._renderModeratorControls() }
166
-                { this._renderDurationAndFormat() }
167
-            </Dialog>
168
-        );
169
-    }
170
-
171
-    /**
172
-     * Renders the recording duration and encoding format. Only shown if local
173
-     * recording is engaged.
174
-     *
175
-     * @private
176
-     * @returns {ReactElement|null}
177
-     */
178
-    _renderDurationAndFormat() {
179
-        const { encodingFormat, isEngaged, t } = this.props;
180
-        const { durationString } = this.state;
181
-
182
-        if (!isEngaged) {
183
-            return null;
184
-        }
185
-
186
-        return (
187
-            <div>
188
-                <div>
189
-                    <span className = 'localrec-control-info-label'>
190
-                        {`${t('localRecording.duration')}:`}
191
-                    </span>
192
-                    <span className = 'info-value'>
193
-                        { durationString === ''
194
-                            ? t('localRecording.durationNA')
195
-                            : durationString }
196
-                    </span>
197
-                </div>
198
-                <div>
199
-                    <span className = 'localrec-control-info-label'>
200
-                        {`${t('localRecording.encoding')}:`}
201
-                    </span>
202
-                    <span className = 'info-value'>
203
-                        { encodingFormat }
204
-                    </span>
205
-                </div>
206
-            </div>
207
-        );
208
-    }
209
-
210
-    /**
211
-     * Returns React elements for displaying the local recording stats of
212
-     * each participant.
213
-     *
214
-     * @private
215
-     * @returns {ReactElement|null}
216
-     */
217
-    _renderStats() {
218
-        const { stats } = this.props;
219
-
220
-        if (stats === undefined) {
221
-            return null;
222
-        }
223
-        const ids = Object.keys(stats);
224
-
225
-        return (
226
-            <div className = 'localrec-participant-stats' >
227
-                { this._renderStatsHeader() }
228
-                { ids.map((id, i) => this._renderStatsLine(i, id)) }
229
-            </div>
230
-        );
231
-    }
232
-
233
-    /**
234
-     * Renders the stats for one participant.
235
-     *
236
-     * @private
237
-     * @param {*} lineKey - The key required by React for elements in lists.
238
-     * @param {*} id - The ID of the participant.
239
-     * @returns {ReactElement}
240
-     */
241
-    _renderStatsLine(lineKey, id) {
242
-        const { stats } = this.props;
243
-        let statusClass = 'localrec-participant-stats-item__status-dot ';
244
-
245
-        statusClass += stats[id].recordingStats
246
-            ? stats[id].recordingStats.isRecording
247
-                ? 'status-on'
248
-                : 'status-off'
249
-            : 'status-unknown';
250
-
251
-        return (
252
-            <div
253
-                className = 'localrec-participant-stats-item'
254
-                key = { lineKey } >
255
-                <div className = 'localrec-participant-stats-item__status'>
256
-                    <span className = { statusClass } />
257
-                </div>
258
-                <div className = 'localrec-participant-stats-item__name'>
259
-                    { stats[id].displayName || id }
260
-                </div>
261
-                <div className = 'localrec-participant-stats-item__sessionid'>
262
-                    { stats[id].recordingStats.currentSessionToken }
263
-                </div>
264
-            </div>
265
-        );
266
-    }
267
-
268
-    /**
269
-     * Renders the participant stats header line.
270
-     *
271
-     * @private
272
-     * @returns {ReactElement}
273
-     */
274
-    _renderStatsHeader() {
275
-        const { t } = this.props;
276
-
277
-        return (
278
-            <div className = 'localrec-participant-stats-item'>
279
-                <div className = 'localrec-participant-stats-item__status' />
280
-                <div className = 'localrec-participant-stats-item__name'>
281
-                    { t('localRecording.participant') }
282
-                </div>
283
-                <div className = 'localrec-participant-stats-item__sessionid'>
284
-                    { t('localRecording.sessionToken') }
285
-                </div>
286
-            </div>
287
-        );
288
-    }
289
-
290
-    /**
291
-     * Renders the moderator-only controls: The stats of all users and the
292
-     * action links.
293
-     *
294
-     * @private
295
-     * @returns {ReactElement|null}
296
-     */
297
-    _renderModeratorControls() {
298
-        const { isModerator, isEngaged, t } = this.props;
299
-
300
-        if (!isModerator) {
301
-            return null;
302
-        }
303
-
304
-        return (
305
-            <div>
306
-                <div className = 'localrec-control-action-links'>
307
-                    <div className = 'localrec-control-action-link'>
308
-                        { isEngaged ? <a
309
-                            onClick = { this._onStop }
310
-                            role = 'button'
311
-                            tabIndex = { 0 }>
312
-                            { t('localRecording.stop') }
313
-                        </a>
314
-                            : <a
315
-                                onClick = { this._onStart }
316
-                                role = 'button'
317
-                                tabIndex = { 0 }>
318
-                                { t('localRecording.start') }
319
-                            </a>
320
-                        }
321
-                    </div>
322
-                </div>
323
-                <div>
324
-                    <span className = 'localrec-control-info-label'>
325
-                        {`${t('localRecording.participantStats')}:`}
326
-                    </span>
327
-                </div>
328
-                { this._renderStats() }
329
-            </div>
330
-        );
331
-    }
332
-
333
-    /**
334
-     * Creates a duration string "HH:MM:SS" from two Date objects.
335
-     *
336
-     * @param {Date} now - Current time.
337
-     * @param {Date} prev - Previous time, the time to be subtracted.
338
-     * @returns {string}
339
-     */
340
-    _getDuration(now, prev) {
341
-        if (prev === null || prev === undefined) {
342
-            return '';
343
-        }
344
-
345
-        // Still a hack, as moment.js does not support formatting of duration
346
-        // (i.e. TimeDelta). Only works if total duration < 24 hours.
347
-        // But who is going to have a 24-hour long conference?
348
-        return moment(now - prev).utc()
349
-            .format('HH:mm:ss');
350
-    }
351
-
352
-    /**
353
-     * Callback function for the Start UI action.
354
-     *
355
-     * @private
356
-     * @returns {void}
357
-     */
358
-    _onStart() {
359
-        recordingController.startRecording();
360
-    }
361
-
362
-    /**
363
-     * Callback function for the Stop UI action.
364
-     *
365
-     * @private
366
-     * @returns {void}
367
-     */
368
-    _onStop() {
369
-        recordingController.stopRecording();
370
-    }
371
-
372
-}
373
-
374
-/**
375
- * Maps (parts of) the Redux state to the associated props for the
376
- * {@code LocalRecordingInfoDialog} component.
377
- *
378
- * @param {Object} state - The Redux state.
379
- * @private
380
- * @returns {{
381
- *     encodingFormat: string,
382
- *     isModerator: boolean,
383
- *     isEngaged: boolean,
384
- *     recordingEngagedAt: Date,
385
- *     stats: Object
386
- * }}
387
- */
388
-function _mapStateToProps(state) {
389
-    const {
390
-        encodingFormat,
391
-        isEngaged,
392
-        recordingEngagedAt,
393
-        stats
394
-    } = state['features/local-recording'];
395
-    const isModerator
396
-        = getLocalParticipant(state).role === PARTICIPANT_ROLE.MODERATOR;
397
-
398
-    return {
399
-        encodingFormat,
400
-        isModerator,
401
-        isEngaged,
402
-        recordingEngagedAt,
403
-        stats
404
-    };
405
-}
406
-
407
-export default translate(connect(_mapStateToProps)(LocalRecordingInfoDialog));

+ 0
- 0
react/features/local-recording/components/LocalRecordingLabel.native.js Zobrazit soubor


+ 0
- 82
react/features/local-recording/components/LocalRecordingLabel.web.js Zobrazit soubor

@@ -1,82 +0,0 @@
1
-// @flow
2
-
3
-import React, { Component } from 'react';
4
-
5
-import { translate } from '../../base/i18n/index';
6
-import { Label } from '../../base/label/index';
7
-import { connect } from '../../base/redux';
8
-import { Tooltip } from '../../base/tooltip';
9
-
10
-
11
-/**
12
- * The type of the React {@code Component} props of {@link LocalRecordingLabel}.
13
- */
14
-type Props = {
15
-
16
-    /**
17
-     * Whether this is the Jibri recorder participant.
18
-     */
19
-     _iAmRecorder: boolean,
20
-
21
-     /**
22
-      * Whether local recording is engaged or not.
23
-      */
24
-     _isEngaged: boolean,
25
-
26
-    /**
27
-     * Invoked to obtain translated strings.
28
-     */
29
-    t: Function,
30
-};
31
-
32
-/**
33
- * React Component for displaying a label when local recording is engaged.
34
- *
35
- * @augments Component
36
- */
37
-class LocalRecordingLabel extends Component<Props> {
38
-
39
-    /**
40
-     * Implements React's {@link Component#render()}.
41
-     *
42
-     * @inheritdoc
43
-     * @returns {ReactElement}
44
-     */
45
-    render() {
46
-        if (!this.props._isEngaged || this.props._iAmRecorder) {
47
-            return null;
48
-        }
49
-
50
-        return (
51
-            <Tooltip
52
-                content = { this.props.t('localRecording.labelToolTip') }
53
-                position = { 'bottom' }>
54
-                <Label
55
-                    className = 'local-rec'
56
-                    text = { this.props.t('localRecording.label') } />
57
-            </Tooltip>
58
-        );
59
-    }
60
-
61
-}
62
-
63
-/**
64
- * Maps (parts of) the Redux state to the associated props for the
65
- * {@code LocalRecordingLabel} component.
66
- *
67
- * @param {Object} state - The Redux state.
68
- * @private
69
- * @returns {{
70
- * }}
71
- */
72
-function _mapStateToProps(state) {
73
-    const { isEngaged } = state['features/local-recording'];
74
-    const { iAmRecorder } = state['features/base/config'];
75
-
76
-    return {
77
-        _isEngaged: isEngaged,
78
-        _iAmRecorder: iAmRecorder
79
-    };
80
-}
81
-
82
-export default translate(connect(_mapStateToProps)(LocalRecordingLabel));

+ 0
- 5
react/features/local-recording/components/index.js Zobrazit soubor

@@ -1,5 +0,0 @@
1
-export { default as LocalRecordingButton } from './LocalRecordingButton';
2
-export { default as LocalRecordingLabel } from './LocalRecordingLabel';
3
-export {
4
-    default as LocalRecordingInfoDialog
5
-} from './LocalRecordingInfoDialog';

+ 0
- 687
react/features/local-recording/controller/RecordingController.js Zobrazit soubor

@@ -1,687 +0,0 @@
1
-/* @flow */
2
-
3
-import Bourne from '@hapi/bourne';
4
-
5
-import { i18next } from '../../base/i18n';
6
-import logger from '../logger';
7
-import {
8
-    FlacAdapter,
9
-    OggAdapter,
10
-    WavAdapter,
11
-    downloadBlob
12
-} from '../recording';
13
-import { sessionManager } from '../session';
14
-
15
-/**
16
- * XMPP command for signaling the start of local recording to all clients.
17
- * Should be sent by the moderator only.
18
- */
19
-const COMMAND_START = 'localRecStart';
20
-
21
-/**
22
- * XMPP command for signaling the stop of local recording to all clients.
23
- * Should be sent by the moderator only.
24
- */
25
-const COMMAND_STOP = 'localRecStop';
26
-
27
-/**
28
- * One-time command used to trigger the moderator to resend the commands.
29
- * This is a workaround for newly-joined clients to receive remote presence.
30
- */
31
-const COMMAND_PING = 'localRecPing';
32
-
33
-/**
34
- * One-time command sent upon receiving a {@code COMMAND_PING}.
35
- * Only the moderator sends this command.
36
- * This command does not carry any information itself, but rather forces the
37
- * XMPP server to resend the remote presence.
38
- */
39
-const COMMAND_PONG = 'localRecPong';
40
-
41
-/**
42
- * Participant property key for local recording stats.
43
- */
44
-const PROPERTY_STATS = 'localRecStats';
45
-
46
-/**
47
- * Supported recording formats.
48
- */
49
-const RECORDING_FORMATS = new Set([ 'flac', 'wav', 'ogg' ]);
50
-
51
-/**
52
- * Default recording format.
53
- */
54
-const DEFAULT_RECORDING_FORMAT = 'flac';
55
-
56
-/**
57
- * States of the {@code RecordingController}.
58
- */
59
-const ControllerState = Object.freeze({
60
-    /**
61
-     * Idle (not recording).
62
-     */
63
-    IDLE: Symbol('IDLE'),
64
-
65
-    /**
66
-     * Starting.
67
-     */
68
-    STARTING: Symbol('STARTING'),
69
-
70
-    /**
71
-     * Engaged (recording).
72
-     */
73
-    RECORDING: Symbol('RECORDING'),
74
-
75
-    /**
76
-     * Stopping.
77
-     */
78
-    STOPPING: Symbol('STOPPING'),
79
-
80
-    /**
81
-     * Failed, due to error during starting / stopping process.
82
-     */
83
-    FAILED: Symbol('FAILED')
84
-});
85
-
86
-/**
87
- * Type of the stats reported by each participant (client).
88
- */
89
-type RecordingStats = {
90
-
91
-    /**
92
-     * Current local recording session token used by the participant.
93
-     */
94
-    currentSessionToken: number,
95
-
96
-    /**
97
-     * Whether local recording is engaged on the participant's device.
98
-     */
99
-    isRecording: boolean,
100
-
101
-    /**
102
-     * Total recorded bytes. (Reserved for future use.).
103
-     */
104
-    recordedBytes: number,
105
-
106
-    /**
107
-     * Total recording duration. (Reserved for future use.).
108
-     */
109
-    recordedLength: number
110
-}
111
-
112
-/**
113
- * The component responsible for the coordination of local recording, across
114
- * multiple participants.
115
- * Current implementation requires that there is only one moderator in a room.
116
- */
117
-class RecordingController {
118
-
119
-    /**
120
-     * For each recording session, there is a separate @{code RecordingAdapter}
121
-     * instance so that encoded bits from the previous sessions can still be
122
-     * retrieved after they ended.
123
-     *
124
-     * @private
125
-     */
126
-    _adapters = {};
127
-
128
-    /**
129
-     * The {@code JitsiConference} instance.
130
-     *
131
-     * @private
132
-     */
133
-    _conference: * = null;
134
-
135
-    /**
136
-     * Current recording session token.
137
-     * Session token is a number generated by the moderator, to ensure every
138
-     * client is in the same recording state.
139
-     *
140
-     * @private
141
-     */
142
-    _currentSessionToken: number = -1;
143
-
144
-    /**
145
-     * Current state of {@code RecordingController}.
146
-     *
147
-     * @private
148
-     */
149
-    _state = ControllerState.IDLE;
150
-
151
-    /**
152
-     * Whether or not the audio is muted in the UI. This is stored as internal
153
-     * state of {@code RecordingController} because we might have recording
154
-     * sessions that start muted.
155
-     */
156
-    _isMuted = false;
157
-
158
-    /**
159
-     * The ID of the active microphone.
160
-     *
161
-     * @private
162
-     */
163
-    _micDeviceId = 'default';
164
-
165
-    /**
166
-     * Current recording format. This will be in effect from the next
167
-     * recording session, i.e., if this value is changed during an on-going
168
-     * recording session, that on-going session will not use the new format.
169
-     *
170
-     * @private
171
-     */
172
-    _format = DEFAULT_RECORDING_FORMAT;
173
-
174
-    /**
175
-     * Whether or not the {@code RecordingController} has registered for
176
-     * XMPP events. Prevents initialization from happening multiple times.
177
-     *
178
-     * @private
179
-     */
180
-    _registered = false;
181
-
182
-    /**
183
-     * FIXME: callback function for the {@code RecordingController} to notify
184
-     * UI it wants to display a notice. Keeps {@code RecordingController}
185
-     * decoupled from UI.
186
-     */
187
-    _onNotify: ?(messageKey: string, messageParams?: Object) => void;
188
-
189
-    /**
190
-     * FIXME: callback function for the {@code RecordingController} to notify
191
-     * UI it wants to display a warning. Keeps {@code RecordingController}
192
-     * decoupled from UI.
193
-     */
194
-    _onWarning: ?(messageKey: string, messageParams?: Object) => void;
195
-
196
-    /**
197
-     * FIXME: callback function for the {@code RecordingController} to notify
198
-     * UI that the local recording state has changed.
199
-     */
200
-    _onStateChanged: ?(boolean) => void;
201
-
202
-    /**
203
-     * Constructor.
204
-     *
205
-     * @returns {void}
206
-     */
207
-    constructor() {
208
-        this.registerEvents = this.registerEvents.bind(this);
209
-        this.getParticipantsStats = this.getParticipantsStats.bind(this);
210
-        this._onStartCommand = this._onStartCommand.bind(this);
211
-        this._onStopCommand = this._onStopCommand.bind(this);
212
-        this._onPingCommand = this._onPingCommand.bind(this);
213
-        this._doStartRecording = this._doStartRecording.bind(this);
214
-        this._doStopRecording = this._doStopRecording.bind(this);
215
-        this._updateStats = this._updateStats.bind(this);
216
-        this._switchToNewSession = this._switchToNewSession.bind(this);
217
-    }
218
-
219
-    registerEvents: () => void;
220
-
221
-    /**
222
-     * Registers listeners for XMPP events.
223
-     *
224
-     * @param {JitsiConference} conference - A {@code JitsiConference} instance.
225
-     * @returns {void}
226
-     */
227
-    registerEvents(conference: Object) {
228
-        if (!this._registered) {
229
-            this._conference = conference;
230
-            if (this._conference) {
231
-                this._conference
232
-                    .addCommandListener(COMMAND_STOP, this._onStopCommand);
233
-                this._conference
234
-                    .addCommandListener(COMMAND_START, this._onStartCommand);
235
-                this._conference
236
-                    .addCommandListener(COMMAND_PING, this._onPingCommand);
237
-                this._registered = true;
238
-            }
239
-            if (!this._conference.isModerator()) {
240
-                this._conference.sendCommandOnce(COMMAND_PING, {});
241
-            }
242
-        }
243
-    }
244
-
245
-    /**
246
-     * Sets the event handler for {@code onStateChanged}.
247
-     *
248
-     * @param {Function} delegate - The event handler.
249
-     * @returns {void}
250
-     */
251
-    set onStateChanged(delegate: Function) {
252
-        this._onStateChanged = delegate;
253
-    }
254
-
255
-    /**
256
-     * Sets the event handler for {@code onNotify}.
257
-     *
258
-     * @param {Function} delegate - The event handler.
259
-     * @returns {void}
260
-     */
261
-    set onNotify(delegate: Function) {
262
-        this._onNotify = delegate;
263
-    }
264
-
265
-    /**
266
-     * Sets the event handler for {@code onWarning}.
267
-     *
268
-     * @param {Function} delegate - The event handler.
269
-     * @returns {void}
270
-     */
271
-    set onWarning(delegate: Function) {
272
-        this._onWarning = delegate;
273
-    }
274
-
275
-    /**
276
-     * Signals the participants to start local recording.
277
-     *
278
-     * @returns {void}
279
-     */
280
-    startRecording() {
281
-        this.registerEvents();
282
-        if (this._conference && this._conference.isModerator()) {
283
-            this._conference.removeCommand(COMMAND_STOP);
284
-            this._conference.sendCommand(COMMAND_START, {
285
-                attributes: {
286
-                    sessionToken: this._getRandomToken(),
287
-                    format: this._format
288
-                }
289
-            });
290
-        } else if (this._onWarning) {
291
-            this._onWarning('localRecording.messages.notModerator');
292
-        }
293
-    }
294
-
295
-    /**
296
-     * Signals the participants to stop local recording.
297
-     *
298
-     * @returns {void}
299
-     */
300
-    stopRecording() {
301
-        if (this._conference) {
302
-            if (this._conference.isModerator()) {
303
-                this._conference.removeCommand(COMMAND_START);
304
-                this._conference.sendCommand(COMMAND_STOP, {
305
-                    attributes: {
306
-                        sessionToken: this._currentSessionToken
307
-                    }
308
-                });
309
-            } else if (this._onWarning) {
310
-                this._onWarning('localRecording.messages.notModerator');
311
-            }
312
-        }
313
-    }
314
-
315
-    /**
316
-     * Triggers the download of recorded data.
317
-     * Browser only.
318
-     *
319
-     * @param {number} sessionToken - The token of the session to download.
320
-     * @returns {void}
321
-     */
322
-    downloadRecordedData(sessionToken: number) {
323
-        if (this._adapters[sessionToken]) {
324
-            this._adapters[sessionToken].exportRecordedData()
325
-                .then(args => {
326
-                    const { data, format } = args;
327
-
328
-                    const filename = `session_${sessionToken}`
329
-                        + `_${this._conference.myUserId()}.${format}`;
330
-
331
-                    downloadBlob(data, filename);
332
-                })
333
-                .catch(error => {
334
-                    logger.error('Failed to download audio for'
335
-                        + ` session ${sessionToken}. Error: ${error}`);
336
-                });
337
-        } else {
338
-            logger.error(`Invalid session token for download ${sessionToken}`);
339
-        }
340
-    }
341
-
342
-    /**
343
-     * Changes the current microphone.
344
-     *
345
-     * @param {string} micDeviceId - The new microphone device ID.
346
-     * @returns {void}
347
-     */
348
-    setMicDevice(micDeviceId: string) {
349
-        if (micDeviceId !== this._micDeviceId) {
350
-            this._micDeviceId = String(micDeviceId);
351
-
352
-            if (this._state === ControllerState.RECORDING) {
353
-                // sessionManager.endSegment(this._currentSessionToken);
354
-                logger.log('Before switching microphone...');
355
-                this._adapters[this._currentSessionToken]
356
-                    .setMicDevice(this._micDeviceId)
357
-                    .then(() => {
358
-                        logger.log('Finished switching microphone.');
359
-
360
-                        // sessionManager.beginSegment(this._currentSesoken);
361
-                    })
362
-                    .catch(() => {
363
-                        logger.error('Failed to switch microphone');
364
-                    });
365
-            }
366
-            logger.log(`Switch microphone to ${this._micDeviceId}`);
367
-        }
368
-    }
369
-
370
-    /**
371
-     * Mute or unmute audio. When muted, the ongoing local recording should
372
-     * produce silence.
373
-     *
374
-     * @param {boolean} muted - If the audio should be muted.
375
-     * @returns {void}
376
-     */
377
-    setMuted(muted: boolean) {
378
-        this._isMuted = Boolean(muted);
379
-
380
-        if (this._state === ControllerState.RECORDING) {
381
-            this._adapters[this._currentSessionToken].setMuted(this._isMuted);
382
-        }
383
-    }
384
-
385
-    /**
386
-     * Switches the recording format.
387
-     *
388
-     * @param {string} newFormat - The new format.
389
-     * @returns {void}
390
-     */
391
-    switchFormat(newFormat: string) {
392
-        if (!RECORDING_FORMATS.has(newFormat)) {
393
-            logger.log(`Unknown format ${newFormat}. Ignoring...`);
394
-
395
-            return;
396
-        }
397
-        this._format = newFormat;
398
-        logger.log(`Recording format switched to ${newFormat}`);
399
-
400
-        // the new format will be used in the next recording session
401
-    }
402
-
403
-    /**
404
-     * Returns the local recording stats.
405
-     *
406
-     * @returns {RecordingStats}
407
-     */
408
-    getLocalStats(): RecordingStats {
409
-        return {
410
-            currentSessionToken: this._currentSessionToken,
411
-            isRecording: this._state === ControllerState.RECORDING,
412
-            recordedBytes: 0,
413
-            recordedLength: 0
414
-        };
415
-    }
416
-
417
-    getParticipantsStats: () => *;
418
-
419
-    /**
420
-     * Returns the remote participants' local recording stats.
421
-     *
422
-     * @returns {*}
423
-     */
424
-    getParticipantsStats() {
425
-        const members
426
-            = this._conference.getParticipants()
427
-            .map(member => {
428
-                return {
429
-                    id: member.getId(),
430
-                    displayName: member.getDisplayName(),
431
-                    recordingStats:
432
-                        Bourne.parse(member.getProperty(PROPERTY_STATS) || '{}'),
433
-                    isSelf: false
434
-                };
435
-            });
436
-
437
-        // transform into a dictionary for consistent ordering
438
-        const result = {};
439
-
440
-        for (let i = 0; i < members.length; ++i) {
441
-            result[members[i].id] = members[i];
442
-        }
443
-        const localId = this._conference.myUserId();
444
-
445
-        result[localId] = {
446
-            id: localId,
447
-            displayName: i18next.t('localRecording.me'),
448
-            recordingStats: this.getLocalStats(),
449
-            isSelf: true
450
-        };
451
-
452
-        return result;
453
-    }
454
-
455
-    _changeState: (symbol) => void;
456
-
457
-    /**
458
-     * Changes the current state of {@code RecordingController}.
459
-     *
460
-     * @private
461
-     * @param {symbol} newState - The new state.
462
-     * @returns {void}
463
-     */
464
-    _changeState(newState: symbol) {
465
-        if (this._state !== newState) {
466
-            logger.log(`state change: ${this._state.toString()} -> `
467
-                + `${newState.toString()}`);
468
-            this._state = newState;
469
-        }
470
-    }
471
-
472
-    _updateStats: () => void;
473
-
474
-    /**
475
-     * Sends out updates about the local recording stats via XMPP.
476
-     *
477
-     * @private
478
-     * @returns {void}
479
-     */
480
-    _updateStats() {
481
-        if (this._conference) {
482
-            this._conference.setLocalParticipantProperty(PROPERTY_STATS,
483
-                JSON.stringify(this.getLocalStats()));
484
-        }
485
-    }
486
-
487
-    _onStartCommand: (*) => void;
488
-
489
-    /**
490
-     * Callback function for XMPP event.
491
-     *
492
-     * @private
493
-     * @param {*} value - The event args.
494
-     * @returns {void}
495
-     */
496
-    _onStartCommand(value) {
497
-        const { sessionToken, format } = value.attributes;
498
-
499
-        if (this._state === ControllerState.IDLE) {
500
-            this._changeState(ControllerState.STARTING);
501
-            this._switchToNewSession(sessionToken, format);
502
-            this._doStartRecording();
503
-        } else if (this._state === ControllerState.RECORDING
504
-            && this._currentSessionToken !== sessionToken) {
505
-            // There is local recording going on, but not for the same session.
506
-            // This means the current state might be out-of-sync with the
507
-            // moderator's, so we need to restart the recording.
508
-            this._changeState(ControllerState.STOPPING);
509
-            this._doStopRecording().then(() => {
510
-                this._changeState(ControllerState.STARTING);
511
-                this._switchToNewSession(sessionToken, format);
512
-                this._doStartRecording();
513
-            });
514
-        }
515
-    }
516
-
517
-    _onStopCommand: (*) => void;
518
-
519
-    /**
520
-     * Callback function for XMPP event.
521
-     *
522
-     * @private
523
-     * @param {*} value - The event args.
524
-     * @returns {void}
525
-     */
526
-    _onStopCommand(value) {
527
-        if (this._state === ControllerState.RECORDING
528
-            && this._currentSessionToken === value.attributes.sessionToken) {
529
-            this._changeState(ControllerState.STOPPING);
530
-            this._doStopRecording();
531
-        }
532
-    }
533
-
534
-    _onPingCommand: () => void;
535
-
536
-    /**
537
-     * Callback function for XMPP event.
538
-     *
539
-     * @private
540
-     * @returns {void}
541
-     */
542
-    _onPingCommand() {
543
-        if (this._conference.isModerator()) {
544
-            logger.log('Received ping, sending pong.');
545
-            this._conference.sendCommandOnce(COMMAND_PONG, {});
546
-        }
547
-    }
548
-
549
-    /**
550
-     * Generates a token that can be used to distinguish each local recording
551
-     * session.
552
-     *
553
-     * @returns {number}
554
-     */
555
-    _getRandomToken() {
556
-        return Math.floor(Math.random() * 100000000) + 1;
557
-    }
558
-
559
-    _doStartRecording: () => void;
560
-
561
-    /**
562
-     * Starts the recording locally.
563
-     *
564
-     * @private
565
-     * @returns {void}
566
-     */
567
-    _doStartRecording() {
568
-        if (this._state === ControllerState.STARTING) {
569
-            const delegate = this._adapters[this._currentSessionToken];
570
-
571
-            delegate.start(this._micDeviceId)
572
-            .then(() => {
573
-                this._changeState(ControllerState.RECORDING);
574
-                sessionManager.beginSegment(this._currentSessionToken);
575
-                logger.log('Local recording engaged.');
576
-
577
-                if (this._onNotify) {
578
-                    this._onNotify('localRecording.messages.engaged');
579
-                }
580
-                if (this._onStateChanged) {
581
-                    this._onStateChanged(true);
582
-                }
583
-
584
-                delegate.setMuted(this._isMuted);
585
-                this._updateStats();
586
-            })
587
-            .catch(err => {
588
-                logger.error('Failed to start local recording.', err);
589
-            });
590
-        }
591
-
592
-    }
593
-
594
-    _doStopRecording: () => Promise<void>;
595
-
596
-    /**
597
-     * Stops the recording locally.
598
-     *
599
-     * @private
600
-     * @returns {Promise<void>}
601
-     */
602
-    _doStopRecording() {
603
-        if (this._state === ControllerState.STOPPING) {
604
-            const token = this._currentSessionToken;
605
-
606
-            return this._adapters[this._currentSessionToken]
607
-                .stop()
608
-                .then(() => {
609
-                    this._changeState(ControllerState.IDLE);
610
-                    sessionManager.endSegment(this._currentSessionToken);
611
-                    logger.log('Local recording unengaged.');
612
-                    this.downloadRecordedData(token);
613
-
614
-                    const messageKey
615
-                        = this._conference.isModerator()
616
-                            ? 'localRecording.messages.finishedModerator'
617
-                            : 'localRecording.messages.finished';
618
-                    const messageParams = {
619
-                        token
620
-                    };
621
-
622
-                    if (this._onNotify) {
623
-                        this._onNotify(messageKey, messageParams);
624
-                    }
625
-                    if (this._onStateChanged) {
626
-                        this._onStateChanged(false);
627
-                    }
628
-                    this._updateStats();
629
-                })
630
-                .catch(err => {
631
-                    logger.error('Failed to stop local recording.', err);
632
-                });
633
-        }
634
-
635
-        /* eslint-disable */
636
-        return (Promise.resolve(): Promise<void>);
637
-        // FIXME: better ways to satisfy flow and ESLint at the same time?
638
-        /* eslint-enable */
639
-
640
-    }
641
-
642
-    _switchToNewSession: (string, string) => void;
643
-
644
-    /**
645
-     * Switches to a new local recording session.
646
-     *
647
-     * @param {string} sessionToken - The session Token.
648
-     * @param {string} format - The recording format for the session.
649
-     * @returns {void}
650
-     */
651
-    _switchToNewSession(sessionToken, format) {
652
-        this._format = format;
653
-        this._currentSessionToken = sessionToken;
654
-        logger.log(`New session: ${this._currentSessionToken}, `
655
-            + `format: ${this._format}`);
656
-        this._adapters[sessionToken]
657
-             = this._createRecordingAdapter();
658
-        sessionManager.createSession(sessionToken, this._format);
659
-    }
660
-
661
-    /**
662
-     * Creates a recording adapter according to the current recording format.
663
-     *
664
-     * @private
665
-     * @returns {RecordingAdapter}
666
-     */
667
-    _createRecordingAdapter() {
668
-        logger.debug('[RecordingController] creating recording'
669
-            + ` adapter for ${this._format} format.`);
670
-
671
-        switch (this._format) {
672
-        case 'ogg':
673
-            return new OggAdapter();
674
-        case 'flac':
675
-            return new FlacAdapter();
676
-        case 'wav':
677
-            return new WavAdapter();
678
-        default:
679
-            throw new Error(`Unknown format: ${this._format}`);
680
-        }
681
-    }
682
-}
683
-
684
-/**
685
- * Global singleton of {@code RecordingController}.
686
- */
687
-export const recordingController = new RecordingController();

+ 0
- 1
react/features/local-recording/controller/index.js Zobrazit soubor

@@ -1 +0,0 @@
1
-export * from './RecordingController';

+ 0
- 4
react/features/local-recording/index.js Zobrazit soubor

@@ -1,4 +0,0 @@
1
-export * from './actions';
2
-export * from './actionTypes';
3
-export * from './components';
4
-export * from './controller';

+ 0
- 5
react/features/local-recording/logger.js Zobrazit soubor

@@ -1,5 +0,0 @@
1
-// @flow
2
-
3
-import { getLogger } from '../base/logging/functions';
4
-
5
-export default getLogger('features/local-recording');

+ 0
- 97
react/features/local-recording/middleware.js Zobrazit soubor

@@ -1,97 +0,0 @@
1
-/* @flow */
2
-
3
-import { createShortcutEvent, sendAnalytics } from '../analytics';
4
-import { APP_WILL_UNMOUNT } from '../base/app/actionTypes';
5
-import { CONFERENCE_JOINED } from '../base/conference/actionTypes';
6
-import { toggleDialog } from '../base/dialog/actions';
7
-import { i18next } from '../base/i18n';
8
-import { SET_AUDIO_MUTED } from '../base/media/actionTypes';
9
-import { MiddlewareRegistry } from '../base/redux';
10
-import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
11
-import { NOTIFICATION_TIMEOUT_TYPE } from '../notifications';
12
-import { showNotification } from '../notifications/actions';
13
-
14
-import { localRecordingEngaged, localRecordingUnengaged } from './actions';
15
-import { LocalRecordingInfoDialog } from './components';
16
-import { recordingController } from './controller';
17
-
18
-declare var APP: Object;
19
-
20
-MiddlewareRegistry.register(({ getState, dispatch }) => next => action => {
21
-    const result = next(action);
22
-
23
-    switch (action.type) {
24
-    case CONFERENCE_JOINED: {
25
-        const { localRecording } = getState()['features/base/config'];
26
-        const isLocalRecordingEnabled = Boolean(
27
-            localRecording
28
-            && localRecording.enabled
29
-            && typeof APP === 'object'
30
-        );
31
-
32
-        if (!isLocalRecordingEnabled) {
33
-            break;
34
-        }
35
-
36
-        // realize the delegates on recordingController, allowing the UI to
37
-        // react to state changes in recordingController.
38
-        recordingController.onStateChanged = isEngaged => {
39
-            if (isEngaged) {
40
-                const nowTime = new Date();
41
-
42
-                dispatch(localRecordingEngaged(nowTime));
43
-            } else {
44
-                dispatch(localRecordingUnengaged());
45
-            }
46
-        };
47
-
48
-        recordingController.onWarning = (messageKey, messageParams) => {
49
-            dispatch(showNotification({
50
-                titleKey: 'localRecording.localRecording',
51
-                description: i18next.t(messageKey, messageParams)
52
-            }, NOTIFICATION_TIMEOUT_TYPE.LONG));
53
-        };
54
-
55
-        recordingController.onNotify = (messageKey, messageParams) => {
56
-            dispatch(showNotification({
57
-                titleKey: 'localRecording.localRecording',
58
-                description: i18next.t(messageKey, messageParams)
59
-            }, NOTIFICATION_TIMEOUT_TYPE.LONG));
60
-        };
61
-
62
-        typeof APP === 'object' && typeof APP.keyboardshortcut === 'object'
63
-            && APP.keyboardshortcut.registerShortcut('L', null, () => {
64
-                sendAnalytics(createShortcutEvent('local.recording'));
65
-                dispatch(toggleDialog(LocalRecordingInfoDialog));
66
-            }, 'keyboardShortcuts.localRecording');
67
-
68
-        if (localRecording.format) {
69
-            recordingController.switchFormat(localRecording.format);
70
-        }
71
-
72
-        const { conference } = getState()['features/base/conference'];
73
-
74
-        recordingController.registerEvents(conference);
75
-
76
-        break;
77
-    }
78
-    case APP_WILL_UNMOUNT:
79
-        recordingController.onStateChanged = null;
80
-        recordingController.onNotify = null;
81
-        recordingController.onWarning = null;
82
-        break;
83
-    case SET_AUDIO_MUTED:
84
-        recordingController.setMuted(action.muted);
85
-        break;
86
-    case SETTINGS_UPDATED: {
87
-        const { micDeviceId } = getState()['features/base/settings'];
88
-
89
-        if (micDeviceId) {
90
-            recordingController.setMicDevice(micDeviceId);
91
-        }
92
-        break;
93
-    }
94
-    }
95
-
96
-    return result;
97
-});

+ 0
- 129
react/features/local-recording/recording/AbstractAudioContextAdapter.js Zobrazit soubor

@@ -1,129 +0,0 @@
1
-import logger from '../logger';
2
-
3
-import { RecordingAdapter } from './RecordingAdapter';
4
-
5
-/**
6
- * Base class for {@code AudioContext}-based recording adapters.
7
- */
8
-export class AbstractAudioContextAdapter extends RecordingAdapter {
9
-    /**
10
-     * The {@code AudioContext} instance.
11
-     */
12
-    _audioContext = null;
13
-
14
-    /**
15
-     * The {@code ScriptProcessorNode} instance.
16
-     */
17
-    _audioProcessingNode = null;
18
-
19
-    /**
20
-     * The {@code MediaStreamAudioSourceNode} instance.
21
-     */
22
-    _audioSource = null;
23
-
24
-    /**
25
-     * The {@code MediaStream} instance, representing the current audio device.
26
-     */
27
-    _stream = null;
28
-
29
-    /**
30
-     * Sample rate.
31
-     */
32
-    _sampleRate = 44100;
33
-
34
-    /**
35
-     * Constructor.
36
-     */
37
-    constructor() {
38
-        super();
39
-
40
-        // sampleRate is browser and OS dependent.
41
-        // Setting sampleRate explicitly is in the specs but not implemented
42
-        // by browsers.
43
-        // See: https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/
44
-        //    AudioContext#Browser_compatibility
45
-        // And https://bugs.chromium.org/p/chromium/issues/detail?id=432248
46
-
47
-        this._audioContext = new AudioContext();
48
-        this._sampleRate = this._audioContext.sampleRate;
49
-        logger.log(`Current sampleRate ${this._sampleRate}.`);
50
-    }
51
-
52
-    /**
53
-     * Sets up the audio graph in the AudioContext.
54
-     *
55
-     * @protected
56
-     * @param {string} micDeviceId - The current microphone device ID.
57
-     * @param {Function} callback - Callback function to
58
-     * handle AudioProcessingEvents.
59
-     * @returns {Promise}
60
-     */
61
-    _initializeAudioContext(micDeviceId, callback) {
62
-        if (typeof callback !== 'function') {
63
-            return Promise.reject('a callback function is required.');
64
-        }
65
-
66
-        return this._getAudioStream(micDeviceId)
67
-        .then(stream => {
68
-            this._stream = stream;
69
-            this._audioSource
70
-                = this._audioContext.createMediaStreamSource(stream);
71
-            this._audioProcessingNode
72
-                = this._audioContext.createScriptProcessor(4096, 1, 1);
73
-            this._audioProcessingNode.onaudioprocess = callback;
74
-            logger.debug('AudioContext is set up.');
75
-        })
76
-        .catch(err => {
77
-            logger.error(`Error calling getUserMedia(): ${err}`);
78
-
79
-            return Promise.reject(err);
80
-        });
81
-    }
82
-
83
-    /**
84
-     * Connects the nodes in the {@code AudioContext} to start the flow of
85
-     * audio data.
86
-     *
87
-     * @protected
88
-     * @returns {void}
89
-     */
90
-    _connectAudioGraph() {
91
-        this._audioSource.connect(this._audioProcessingNode);
92
-        this._audioProcessingNode.connect(this._audioContext.destination);
93
-    }
94
-
95
-    /**
96
-     * Disconnects the nodes in the {@code AudioContext}.
97
-     *
98
-     * @protected
99
-     * @returns {void}
100
-     */
101
-    _disconnectAudioGraph() {
102
-        this._audioProcessingNode.onaudioprocess = undefined;
103
-        this._audioProcessingNode.disconnect();
104
-        this._audioSource.disconnect();
105
-    }
106
-
107
-    /**
108
-     * Replaces the current microphone MediaStream.
109
-     *
110
-     * @protected
111
-     * @param {string} micDeviceId - New microphone ID.
112
-     * @returns {Promise}
113
-     */
114
-    _replaceMic(micDeviceId) {
115
-        if (this._audioContext && this._audioProcessingNode) {
116
-            return this._getAudioStream(micDeviceId).then(newStream => {
117
-                const newSource = this._audioContext
118
-                    .createMediaStreamSource(newStream);
119
-
120
-                this._audioSource.disconnect();
121
-                newSource.connect(this._audioProcessingNode);
122
-                this._stream = newStream;
123
-                this._audioSource = newSource;
124
-            });
125
-        }
126
-
127
-        return Promise.resolve();
128
-    }
129
-}

+ 0
- 146
react/features/local-recording/recording/OggAdapter.js Zobrazit soubor

@@ -1,146 +0,0 @@
1
-import logger from '../logger';
2
-
3
-import { RecordingAdapter } from './RecordingAdapter';
4
-
5
-/**
6
- * Recording adapter that uses {@code MediaRecorder} (default browser encoding
7
- * with Opus codec).
8
- */
9
-export class OggAdapter extends RecordingAdapter {
10
-
11
-    /**
12
-     * Instance of MediaRecorder.
13
-     *
14
-     * @private
15
-     */
16
-    _mediaRecorder = null;
17
-
18
-    /**
19
-     * Initialization promise.
20
-     *
21
-     * @private
22
-     */
23
-    _initPromise = null;
24
-
25
-    /**
26
-     * The recorded audio file.
27
-     *
28
-     * @private
29
-     */
30
-    _recordedData = null;
31
-
32
-    /**
33
-     * Implements {@link RecordingAdapter#start()}.
34
-     *
35
-     * @inheritdoc
36
-     */
37
-    start(micDeviceId) {
38
-        if (!this._initPromise) {
39
-            this._initPromise = this._initialize(micDeviceId);
40
-        }
41
-
42
-        return this._initPromise.then(() =>
43
-            new Promise(resolve => {
44
-                this._mediaRecorder.start();
45
-                resolve();
46
-            })
47
-        );
48
-    }
49
-
50
-    /**
51
-     * Implements {@link RecordingAdapter#stop()}.
52
-     *
53
-     * @inheritdoc
54
-     */
55
-    stop() {
56
-        return new Promise(
57
-            resolve => {
58
-                this._mediaRecorder.onstop = () => resolve();
59
-                this._mediaRecorder.stop();
60
-            }
61
-        );
62
-    }
63
-
64
-    /**
65
-     * Implements {@link RecordingAdapter#exportRecordedData()}.
66
-     *
67
-     * @inheritdoc
68
-     */
69
-    exportRecordedData() {
70
-        if (this._recordedData !== null) {
71
-            return Promise.resolve({
72
-                data: this._recordedData,
73
-                format: 'ogg'
74
-            });
75
-        }
76
-
77
-        return Promise.reject('No audio data recorded.');
78
-    }
79
-
80
-    /**
81
-     * Implements {@link RecordingAdapter#setMuted()}.
82
-     *
83
-     * @inheritdoc
84
-     */
85
-    setMuted(muted) {
86
-        const shouldEnable = !muted;
87
-
88
-        if (!this._stream) {
89
-            return Promise.resolve();
90
-        }
91
-
92
-        const track = this._stream.getAudioTracks()[0];
93
-
94
-        if (!track) {
95
-            logger.error('Cannot mute/unmute. Track not found!');
96
-
97
-            return Promise.resolve();
98
-        }
99
-
100
-        if (track.enabled !== shouldEnable) {
101
-            track.enabled = shouldEnable;
102
-            logger.log(muted ? 'Mute' : 'Unmute');
103
-        }
104
-
105
-        return Promise.resolve();
106
-    }
107
-
108
-    /**
109
-     * Initialize the adapter.
110
-     *
111
-     * @private
112
-     * @param {string} micDeviceId - The current microphone device ID.
113
-     * @returns {Promise}
114
-     */
115
-    _initialize(micDeviceId) {
116
-        if (this._mediaRecorder) {
117
-            return Promise.resolve();
118
-        }
119
-
120
-        return new Promise((resolve, error) => {
121
-            this._getAudioStream(micDeviceId)
122
-            .then(stream => {
123
-                this._stream = stream;
124
-                this._mediaRecorder = new MediaRecorder(stream);
125
-                this._mediaRecorder.ondataavailable
126
-                    = e => this._saveMediaData(e.data);
127
-                resolve();
128
-            })
129
-            .catch(err => {
130
-                logger.error(`Error calling getUserMedia(): ${err}`);
131
-                error();
132
-            });
133
-        });
134
-    }
135
-
136
-    /**
137
-     * Callback for storing the encoded data.
138
-     *
139
-     * @private
140
-     * @param {Blob} data - Encoded data.
141
-     * @returns {void}
142
-     */
143
-    _saveMediaData(data) {
144
-        this._recordedData = data;
145
-    }
146
-}

+ 0
- 85
react/features/local-recording/recording/RecordingAdapter.js Zobrazit soubor

@@ -1,85 +0,0 @@
1
-import JitsiMeetJS from '../../base/lib-jitsi-meet';
2
-
3
-/**
4
- * Base class for recording backends.
5
- */
6
-export class RecordingAdapter {
7
-
8
-    /**
9
-     * Starts recording.
10
-     *
11
-     * @param {string} micDeviceId - The microphone to record on.
12
-     * @returns {Promise}
13
-     */
14
-    start(/* eslint-disable no-unused-vars */
15
-            micDeviceId/* eslint-enable no-unused-vars */) {
16
-        throw new Error('Not implemented');
17
-    }
18
-
19
-    /**
20
-     * Stops recording.
21
-     *
22
-     * @returns {Promise}
23
-     */
24
-    stop() {
25
-        throw new Error('Not implemented');
26
-    }
27
-
28
-    /**
29
-     * Export the recorded and encoded audio file.
30
-     *
31
-     * @returns {Promise<Object>}
32
-     */
33
-    exportRecordedData() {
34
-        throw new Error('Not implemented');
35
-    }
36
-
37
-    /**
38
-     * Mutes or unmutes the current recording.
39
-     *
40
-     * @param {boolean} muted - Whether to mute or to unmute.
41
-     * @returns {Promise}
42
-     */
43
-    setMuted(/* eslint-disable no-unused-vars */
44
-            muted/* eslint-enable no-unused-vars */) {
45
-        throw new Error('Not implemented');
46
-    }
47
-
48
-    /**
49
-     * Changes the current microphone.
50
-     *
51
-     * @param {string} micDeviceId - The new microphone device ID.
52
-     * @returns {Promise}
53
-     */
54
-    setMicDevice(/* eslint-disable no-unused-vars */
55
-            micDeviceId/* eslint-enable no-unused-vars */) {
56
-        throw new Error('Not implemented');
57
-    }
58
-
59
-    /**
60
-     * Helper method for getting an audio {@code MediaStream}. Use this instead
61
-     * of calling browser APIs directly.
62
-     *
63
-     * @protected
64
-     * @param {number} micDeviceId - The ID of the current audio device.
65
-     * @returns {Promise}
66
-     */
67
-    _getAudioStream(micDeviceId) {
68
-        return JitsiMeetJS.createLocalTracks({
69
-            devices: [ 'audio' ],
70
-            micDeviceId
71
-        }).then(result => {
72
-            if (result.length !== 1) {
73
-                throw new Error('Unexpected number of streams '
74
-                    + 'from createLocalTracks.');
75
-            }
76
-            const mediaStream = result[0].stream;
77
-
78
-            if (mediaStream === undefined) {
79
-                throw new Error('Failed to create local track.');
80
-            }
81
-
82
-            return mediaStream;
83
-        });
84
-    }
85
-}

+ 0
- 20
react/features/local-recording/recording/Utils.js Zobrazit soubor

@@ -1,20 +0,0 @@
1
-/**
2
- * Force download of Blob in browser by faking an <a> tag.
3
- *
4
- * @param {Blob} blob - Base64 URL.
5
- * @param {string} fileName - The filename to appear in the download dialog.
6
- * @returns {void}
7
- */
8
-export function downloadBlob(blob, fileName = 'recording.ogg') {
9
-    const base64Url = window.URL.createObjectURL(blob);
10
-
11
-    // fake a anchor tag
12
-    const a = document.createElement('a');
13
-
14
-    a.style = 'display: none';
15
-    a.href = base64Url;
16
-    a.download = fileName;
17
-    document.body.appendChild(a);
18
-    a.click();
19
-    document.body.removeChild(a);
20
-}

+ 0
- 290
react/features/local-recording/recording/WavAdapter.js Zobrazit soubor

@@ -1,290 +0,0 @@
1
-import logger from '../logger';
2
-
3
-import { AbstractAudioContextAdapter } from './AbstractAudioContextAdapter';
4
-
5
-const WAV_BITS_PER_SAMPLE = 16;
6
-
7
-/**
8
- * Recording adapter for raw WAVE format.
9
- */
10
-export class WavAdapter extends AbstractAudioContextAdapter {
11
-
12
-    /**
13
-     * Length of the WAVE file, in number of samples.
14
-     */
15
-    _wavLength = 0;
16
-
17
-    /**
18
-     * The {@code ArrayBuffer}s that stores the PCM bits.
19
-     */
20
-    _wavBuffers = [];
21
-
22
-    /**
23
-     * Whether or not the {@code WavAdapter} is in a ready state.
24
-     */
25
-    _isInitialized = false;
26
-
27
-    /**
28
-     * Initialization promise.
29
-     */
30
-    _initPromise = null;
31
-
32
-    /**
33
-     * Constructor.
34
-     */
35
-    constructor() {
36
-        super();
37
-        this._onAudioProcess = this._onAudioProcess.bind(this);
38
-    }
39
-
40
-    /**
41
-     * Implements {@link RecordingAdapter#start()}.
42
-     *
43
-     * @inheritdoc
44
-     */
45
-    start(micDeviceId) {
46
-        if (!this._initPromise) {
47
-            this._initPromise = this._initialize(micDeviceId);
48
-        }
49
-
50
-        return this._initPromise.then(() => {
51
-            this._wavBuffers = [];
52
-            this._wavLength = 0;
53
-
54
-            this._connectAudioGraph();
55
-        });
56
-    }
57
-
58
-    /**
59
-     * Implements {@link RecordingAdapter#stop()}.
60
-     *
61
-     * @inheritdoc
62
-     */
63
-    stop() {
64
-        this._disconnectAudioGraph();
65
-        this._data = this._exportMonoWAV(this._wavBuffers, this._wavLength);
66
-        this._audioProcessingNode = null;
67
-        this._audioSource = null;
68
-        this._isInitialized = false;
69
-
70
-        return Promise.resolve();
71
-    }
72
-
73
-    /**
74
-     * Implements {@link RecordingAdapter#exportRecordedData()}.
75
-     *
76
-     * @inheritdoc
77
-     */
78
-    exportRecordedData() {
79
-        if (this._data !== null) {
80
-            return Promise.resolve({
81
-                data: this._data,
82
-                format: 'wav'
83
-            });
84
-        }
85
-
86
-        return Promise.reject('No audio data recorded.');
87
-    }
88
-
89
-    /**
90
-     * Implements {@link RecordingAdapter#setMuted()}.
91
-     *
92
-     * @inheritdoc
93
-     */
94
-    setMuted(muted) {
95
-        const shouldEnable = !muted;
96
-
97
-        if (!this._stream) {
98
-            return Promise.resolve();
99
-        }
100
-
101
-        const track = this._stream.getAudioTracks()[0];
102
-
103
-        if (!track) {
104
-            logger.error('Cannot mute/unmute. Track not found!');
105
-
106
-            return Promise.resolve();
107
-        }
108
-
109
-        if (track.enabled !== shouldEnable) {
110
-            track.enabled = shouldEnable;
111
-            logger.log(muted ? 'Mute' : 'Unmute');
112
-        }
113
-
114
-        return Promise.resolve();
115
-    }
116
-
117
-    /**
118
-     * Implements {@link RecordingAdapter#setMicDevice()}.
119
-     *
120
-     * @inheritdoc
121
-     */
122
-    setMicDevice(micDeviceId) {
123
-        return this._replaceMic(micDeviceId);
124
-    }
125
-
126
-    /**
127
-     * Creates a WAVE file header.
128
-     *
129
-     * @private
130
-     * @param {number} dataLength - Length of the payload (PCM data), in bytes.
131
-     * @returns {Uint8Array}
132
-     */
133
-    _createWavHeader(dataLength) {
134
-        // adapted from
135
-        // https://github.com/mmig/speech-to-flac/blob/master/encoder.js
136
-
137
-        // ref: http://soundfile.sapp.org/doc/WaveFormat/
138
-
139
-        // create our WAVE file header
140
-        const buffer = new ArrayBuffer(44);
141
-        const view = new DataView(buffer);
142
-
143
-        // RIFF chunk descriptor
144
-        writeUTFBytes(view, 0, 'RIFF');
145
-
146
-        // set file size at the end
147
-        writeUTFBytes(view, 8, 'WAVE');
148
-
149
-        // FMT sub-chunk
150
-        writeUTFBytes(view, 12, 'fmt ');
151
-        view.setUint32(16, 16, true);
152
-        view.setUint16(20, 1, true);
153
-
154
-        // NumChannels
155
-        view.setUint16(22, 1, true);
156
-
157
-        // SampleRate
158
-        view.setUint32(24, this._sampleRate, true);
159
-
160
-        // ByteRate
161
-        view.setUint32(28,
162
-            Number(this._sampleRate) * 1 * WAV_BITS_PER_SAMPLE / 8, true);
163
-
164
-        // BlockAlign
165
-        view.setUint16(32, 1 * Number(WAV_BITS_PER_SAMPLE) / 8, true);
166
-
167
-        view.setUint16(34, WAV_BITS_PER_SAMPLE, true);
168
-
169
-        // data sub-chunk
170
-        writeUTFBytes(view, 36, 'data');
171
-
172
-        // file length
173
-        view.setUint32(4, 32 + dataLength, true);
174
-
175
-        // data chunk length
176
-        view.setUint32(40, dataLength, true);
177
-
178
-        return new Uint8Array(buffer);
179
-    }
180
-
181
-    /**
182
-     * Initialize the adapter.
183
-     *
184
-     * @private
185
-     * @param {string} micDeviceId - The current microphone device ID.
186
-     * @returns {Promise}
187
-     */
188
-    _initialize(micDeviceId) {
189
-        if (this._isInitialized) {
190
-            return Promise.resolve();
191
-        }
192
-
193
-        return this._initializeAudioContext(micDeviceId, this._onAudioProcess)
194
-            .then(() => {
195
-                this._isInitialized = true;
196
-            });
197
-    }
198
-
199
-    /**
200
-     * Callback function for handling AudioProcessingEvents.
201
-     *
202
-     * @private
203
-     * @param {AudioProcessingEvent} e - The event containing the raw PCM.
204
-     * @returns {void}
205
-     */
206
-    _onAudioProcess(e) {
207
-        // See: https://developer.mozilla.org/en-US/docs/Web/API/
208
-        //      AudioBuffer/getChannelData
209
-        // The returned value is an Float32Array.
210
-        const channelLeft = e.inputBuffer.getChannelData(0);
211
-
212
-        // Need to copy the Float32Array:
213
-        // unlike passing to WebWorker, this data is passed by reference,
214
-        // so we need to copy it, otherwise the resulting audio file will be
215
-        // just repeating the last segment.
216
-        this._wavBuffers.push(new Float32Array(channelLeft));
217
-        this._wavLength += channelLeft.length;
218
-    }
219
-
220
-    /**
221
-     * Combines buffers and export to a wav file.
222
-     *
223
-     * @private
224
-     * @param {Float32Array[]} buffers - The stored buffers.
225
-     * @param {number} length - Total length (number of samples).
226
-     * @returns {Blob}
227
-     */
228
-    _exportMonoWAV(buffers, length) {
229
-        const dataLength = length * 2; // each sample = 16 bit = 2 bytes
230
-        const buffer = new ArrayBuffer(44 + dataLength);
231
-        const view = new DataView(buffer);
232
-
233
-        // copy WAV header data into the array buffer
234
-        const header = this._createWavHeader(dataLength);
235
-        const len = header.length;
236
-
237
-        for (let i = 0; i < len; ++i) {
238
-            view.setUint8(i, header[i]);
239
-        }
240
-
241
-        // write audio data
242
-        floatTo16BitPCM(view, 44, buffers);
243
-
244
-        return new Blob([ view ], { type: 'audio/wav' });
245
-    }
246
-}
247
-
248
-
249
-/**
250
- * Helper function. Writes a UTF string to memory
251
- * using big endianness. Required by WAVE headers.
252
- *
253
- * @param {ArrayBuffer} view - The view to memory.
254
- * @param {number} offset - Offset.
255
- * @param {string} string - The string to be written.
256
- * @returns {void}
257
- */
258
-function writeUTFBytes(view, offset, string) {
259
-    const lng = string.length;
260
-
261
-    // convert to big endianness
262
-    for (let i = 0; i < lng; ++i) {
263
-        view.setUint8(offset + i, string.charCodeAt(i));
264
-    }
265
-}
266
-
267
-/**
268
- * Helper function for converting Float32Array to Int16Array.
269
- *
270
- * @param {DataView} output - View to the output buffer.
271
- * @param {number} offset - The offset in output buffer to write from.
272
- * @param {Float32Array[]} inputBuffers - The input buffers.
273
- * @returns {void}
274
- */
275
-function floatTo16BitPCM(output, offset, inputBuffers) {
276
-
277
-    let i, j;
278
-    let input, s, sampleCount;
279
-    const bufferCount = inputBuffers.length;
280
-    let o = offset;
281
-
282
-    for (i = 0; i < bufferCount; ++i) {
283
-        input = inputBuffers[i];
284
-        sampleCount = input.length;
285
-        for (j = 0; j < sampleCount; ++j, o += 2) {
286
-            s = Math.max(-1, Math.min(1, input[j]));
287
-            output.setInt16(o, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
288
-        }
289
-    }
290
-}

+ 0
- 262
react/features/local-recording/recording/flac/FlacAdapter.js Zobrazit soubor

@@ -1,262 +0,0 @@
1
-import logger from '../../logger';
2
-import { AbstractAudioContextAdapter } from '../AbstractAudioContextAdapter';
3
-
4
-import {
5
-    DEBUG,
6
-    MAIN_THREAD_FINISH,
7
-    MAIN_THREAD_INIT,
8
-    MAIN_THREAD_NEW_DATA_ARRIVED,
9
-    WORKER_BLOB_READY,
10
-    WORKER_LIBFLAC_READY
11
-} from './messageTypes';
12
-
13
-
14
-/**
15
- * Recording adapter that uses libflac.js in the background.
16
- */
17
-export class FlacAdapter extends AbstractAudioContextAdapter {
18
-
19
-    /**
20
-     * Instance of WebWorker (flacEncodeWorker).
21
-     */
22
-    _encoder = null;
23
-
24
-    /**
25
-     * Resolve function of the Promise returned by {@code stop()}.
26
-     * This is called after the WebWorker sends back {@code WORKER_BLOB_READY}.
27
-     */
28
-    _stopPromiseResolver = null;
29
-
30
-    /**
31
-     * Resolve function of the Promise that initializes the flacEncodeWorker.
32
-     */
33
-    _initWorkerPromiseResolver = null;
34
-
35
-    /**
36
-     * Initialization promise.
37
-     */
38
-    _initPromise = null;
39
-
40
-    /**
41
-     * Constructor.
42
-     */
43
-    constructor() {
44
-        super();
45
-        this._onAudioProcess = this._onAudioProcess.bind(this);
46
-        this._onWorkerMessage = this._onWorkerMessage.bind(this);
47
-    }
48
-
49
-    /**
50
-     * Implements {@link RecordingAdapter#start()}.
51
-     *
52
-     * @inheritdoc
53
-     */
54
-    start(micDeviceId) {
55
-        if (!this._initPromise) {
56
-            this._initPromise = this._initialize(micDeviceId);
57
-        }
58
-
59
-        return this._initPromise.then(() => {
60
-            this._connectAudioGraph();
61
-        });
62
-    }
63
-
64
-    /**
65
-     * Implements {@link RecordingAdapter#stop()}.
66
-     *
67
-     * @inheritdoc
68
-     */
69
-    stop() {
70
-        if (!this._encoder) {
71
-            logger.error('Attempting to stop but has nothing to stop.');
72
-
73
-            return Promise.reject();
74
-        }
75
-
76
-        return new Promise(resolve => {
77
-            this._initPromise = null;
78
-            this._disconnectAudioGraph();
79
-            this._stopPromiseResolver = resolve;
80
-            this._encoder.postMessage({
81
-                command: MAIN_THREAD_FINISH
82
-            });
83
-        });
84
-    }
85
-
86
-    /**
87
-     * Implements {@link RecordingAdapter#exportRecordedData()}.
88
-     *
89
-     * @inheritdoc
90
-     */
91
-    exportRecordedData() {
92
-        if (this._data !== null) {
93
-            return Promise.resolve({
94
-                data: this._data,
95
-                format: 'flac'
96
-            });
97
-        }
98
-
99
-        return Promise.reject('No audio data recorded.');
100
-    }
101
-
102
-    /**
103
-     * Implements {@link RecordingAdapter#setMuted()}.
104
-     *
105
-     * @inheritdoc
106
-     */
107
-    setMuted(muted) {
108
-        const shouldEnable = !muted;
109
-
110
-        if (!this._stream) {
111
-            return Promise.resolve();
112
-        }
113
-
114
-        const track = this._stream.getAudioTracks()[0];
115
-
116
-        if (!track) {
117
-            logger.error('Cannot mute/unmute. Track not found!');
118
-
119
-            return Promise.resolve();
120
-        }
121
-
122
-        if (track.enabled !== shouldEnable) {
123
-            track.enabled = shouldEnable;
124
-            logger.log(muted ? 'Mute' : 'Unmute');
125
-        }
126
-
127
-        return Promise.resolve();
128
-    }
129
-
130
-    /**
131
-     * Implements {@link RecordingAdapter#setMicDevice()}.
132
-     *
133
-     * @inheritdoc
134
-     */
135
-    setMicDevice(micDeviceId) {
136
-        return this._replaceMic(micDeviceId);
137
-    }
138
-
139
-    /**
140
-     * Initialize the adapter.
141
-     *
142
-     * @private
143
-     * @param {string} micDeviceId - The current microphone device ID.
144
-     * @returns {Promise}
145
-     */
146
-    _initialize(micDeviceId) {
147
-        if (this._encoder !== null) {
148
-            return Promise.resolve();
149
-        }
150
-
151
-        const promiseInitWorker = new Promise((resolve, reject) => {
152
-            try {
153
-                this._loadWebWorker();
154
-            } catch (e) {
155
-                reject();
156
-            }
157
-
158
-            // Save the Promise's resolver to resolve it later.
159
-            // This Promise is only resolved in _onWorkerMessage when we
160
-            // receive WORKER_LIBFLAC_READY from the WebWorker.
161
-            this._initWorkerPromiseResolver = resolve;
162
-
163
-            // set up listener for messages from the WebWorker
164
-            this._encoder.onmessage = this._onWorkerMessage;
165
-
166
-            this._encoder.postMessage({
167
-                command: MAIN_THREAD_INIT,
168
-                config: {
169
-                    sampleRate: this._sampleRate,
170
-                    bps: 16
171
-                }
172
-            });
173
-        });
174
-
175
-        // Arrow function is used here because we want AudioContext to be
176
-        // initialized only **after** promiseInitWorker is resolved.
177
-        return promiseInitWorker
178
-            .then(() =>
179
-                this._initializeAudioContext(
180
-                    micDeviceId,
181
-                    this._onAudioProcess
182
-                ));
183
-    }
184
-
185
-    /**
186
-     * Callback function for handling AudioProcessingEvents.
187
-     *
188
-     * @private
189
-     * @param {AudioProcessingEvent} e - The event containing the raw PCM.
190
-     * @returns {void}
191
-     */
192
-    _onAudioProcess(e) {
193
-        // Delegates to the WebWorker to do the encoding.
194
-        // The return of getChannelData() is a Float32Array,
195
-        // each element representing one sample.
196
-        const channelLeft = e.inputBuffer.getChannelData(0);
197
-
198
-        this._encoder.postMessage({
199
-            command: MAIN_THREAD_NEW_DATA_ARRIVED,
200
-            buf: channelLeft
201
-        });
202
-    }
203
-
204
-    /**
205
-     * Handler for messages from flacEncodeWorker.
206
-     *
207
-     * @private
208
-     * @param {MessageEvent} e - The event sent by the WebWorker.
209
-     * @returns {void}
210
-     */
211
-    _onWorkerMessage(e) {
212
-        switch (e.data.command) {
213
-        case WORKER_BLOB_READY:
214
-            // Received a Blob representing an encoded FLAC file.
215
-            this._data = e.data.buf;
216
-            if (this._stopPromiseResolver !== null) {
217
-                this._stopPromiseResolver();
218
-                this._stopPromiseResolver = null;
219
-                this._encoder.terminate();
220
-                this._encoder = null;
221
-            }
222
-            break;
223
-        case DEBUG:
224
-            logger.log(e.data);
225
-            break;
226
-        case WORKER_LIBFLAC_READY:
227
-            logger.log('libflac is ready.');
228
-            this._initWorkerPromiseResolver();
229
-            break;
230
-        default:
231
-            logger.error(
232
-                `Unknown event
233
-                from encoder (WebWorker): "${e.data.command}"!`);
234
-            break;
235
-        }
236
-    }
237
-
238
-    /**
239
-     * Loads the WebWorker.
240
-     *
241
-     * @private
242
-     * @returns {void}
243
-     */
244
-    _loadWebWorker() {
245
-        // FIXME: Workaround for different file names in development/
246
-        // production environments.
247
-        // We cannot import flacEncodeWorker as a webpack module,
248
-        // because it is in a different bundle and should be lazy-loaded
249
-        // only when flac recording is in use.
250
-        try {
251
-            // try load the minified version first
252
-            this._encoder = new Worker('/libs/flacEncodeWorker.min.js', { name: 'FLAC encoder worker' });
253
-        } catch (exception1) {
254
-            // if failed, try unminified version
255
-            try {
256
-                this._encoder = new Worker('/libs/flacEncodeWorker.js', { name: 'FLAC encoder worker' });
257
-            } catch (exception2) {
258
-                throw new Error('Failed to load flacEncodeWorker.');
259
-            }
260
-        }
261
-    }
262
-}

+ 0
- 399
react/features/local-recording/recording/flac/flacEncodeWorker.js Zobrazit soubor

@@ -1,399 +0,0 @@
1
-
2
-import {
3
-    MAIN_THREAD_FINISH,
4
-    MAIN_THREAD_INIT,
5
-    MAIN_THREAD_NEW_DATA_ARRIVED,
6
-    WORKER_BLOB_READY,
7
-    WORKER_LIBFLAC_READY
8
-} from './messageTypes';
9
-
10
-/**
11
- * WebWorker that does FLAC encoding using libflac.js.
12
- */
13
-
14
-self.FLAC_SCRIPT_LOCATION = '/libs/';
15
-/* eslint-disable */
16
-importScripts('/libs/libflac4-1.3.2.min.js');
17
-/* eslint-enable */
18
-
19
-// There is a number of API calls to libflac.js, which does not conform
20
-// to the camalCase naming convention, but we cannot change it.
21
-// So we disable the ESLint rule `new-cap` in this file.
22
-/* eslint-disable new-cap */
23
-
24
-// Flow will complain about the number keys in `FLAC_ERRORS`,
25
-// ESLint will complain about the `declare` statement.
26
-// As the current workaround, add an exception for eslint.
27
-/* eslint-disable flowtype/no-types-missing-file-annotation */
28
-declare var Flac: Object;
29
-
30
-const FLAC_ERRORS = {
31
-    // The encoder is in the normal OK state and samples can be processed.
32
-    0: 'FLAC__STREAM_ENCODER_OK',
33
-
34
-    // The encoder is in the uninitialized state one of the
35
-    // FLAC__stream_encoder_init_*() functions must be called before samples can
36
-    // be processed.
37
-    1: 'FLAC__STREAM_ENCODER_UNINITIALIZED',
38
-
39
-    // An error occurred in the underlying Ogg layer.
40
-    2: 'FLAC__STREAM_ENCODER_OGG_ERROR',
41
-
42
-    // An error occurred in the underlying verify stream decoder; check
43
-    // FLAC__stream_encoder_get_verify_decoder_state().
44
-    3: 'FLAC__STREAM_ENCODER_VERIFY_DECODER_ERROR',
45
-
46
-    // The verify decoder detected a mismatch between the original audio signal
47
-    // and the decoded audio signal.
48
-    4: 'FLAC__STREAM_ENCODER_VERIFY_MISMATCH_IN_AUDIO_DATA',
49
-
50
-    // One of the callbacks returned a fatal error.
51
-    5: 'FLAC__STREAM_ENCODER_CLIENT_ERROR',
52
-
53
-    // An I/O error occurred while opening/reading/writing a file. Check errno.
54
-    6: 'FLAC__STREAM_ENCODER_IO_ERROR',
55
-
56
-    // An error occurred while writing the stream; usually, the write_callback
57
-    // returned an error.
58
-    7: 'FLAC__STREAM_ENCODER_FRAMING_ERROR',
59
-
60
-    // Memory allocation failed.
61
-    8: 'FLAC__STREAM_ENCODER_MEMORY_ALLOCATION_ERROR'
62
-};
63
-
64
-/**
65
- * States of the {@code Encoder}.
66
- */
67
-const EncoderState = Object.freeze({
68
-    /**
69
-     * Initial state, when libflac.js is not initialized.
70
-     */
71
-    UNINTIALIZED: 'uninitialized',
72
-
73
-    /**
74
-     * Actively encoding new audio bits.
75
-     */
76
-    WORKING: 'working',
77
-
78
-    /**
79
-     * Encoding has finished and encoded bits are available.
80
-     */
81
-    FINISHED: 'finished'
82
-});
83
-
84
-/**
85
- * Default FLAC compression level.
86
- */
87
-const FLAC_COMPRESSION_LEVEL = 5;
88
-
89
-/**
90
- * Concat multiple Uint8Arrays into one.
91
- *
92
- * @param {Uint8Array[]} arrays - Array of Uint8 arrays.
93
- * @param {number} totalLength - Total length of all Uint8Arrays.
94
- * @returns {Uint8Array}
95
- */
96
-function mergeUint8Arrays(arrays, totalLength) {
97
-    const result = new Uint8Array(totalLength);
98
-    let offset = 0;
99
-    const len = arrays.length;
100
-
101
-    for (let i = 0; i < len; i++) {
102
-        const buffer = arrays[i];
103
-
104
-        result.set(buffer, offset);
105
-        offset += buffer.length;
106
-    }
107
-
108
-    return result;
109
-}
110
-
111
-/**
112
- * Wrapper class around libflac API.
113
- */
114
-class Encoder {
115
-
116
-    /**
117
-     * Flac encoder instance ID. (As per libflac.js API).
118
-     *
119
-     * @private
120
-     */
121
-    _encoderId = 0;
122
-
123
-    /**
124
-     * Sample rate.
125
-     *
126
-     * @private
127
-     */
128
-    _sampleRate;
129
-
130
-    /**
131
-     * Bit depth (bits per sample).
132
-     *
133
-     * @private
134
-     */
135
-    _bitDepth;
136
-
137
-    /**
138
-     * Buffer size.
139
-     *
140
-     * @private
141
-     */
142
-    _bufferSize;
143
-
144
-    /**
145
-     * Buffers to store encoded bits temporarily.
146
-     */
147
-    _flacBuffers = [];
148
-
149
-    /**
150
-     * Length of encoded FLAC bits.
151
-     */
152
-    _flacLength = 0;
153
-
154
-    /**
155
-     * The current state of the {@code Encoder}.
156
-     */
157
-    _state = EncoderState.UNINTIALIZED;
158
-
159
-    /**
160
-     * The ready-for-grab downloadable Blob.
161
-     */
162
-    _data = null;
163
-
164
-
165
-    /**
166
-     * Constructor.
167
-     * Note: Only create instance when Flac.isReady() returns true.
168
-     *
169
-     * @param {number} sampleRate - Sample rate of the raw audio data.
170
-     * @param {number} bitDepth - Bit depth (bit per sample).
171
-     * @param {number} bufferSize - The size of each batch.
172
-     */
173
-    constructor(sampleRate, bitDepth = 16, bufferSize = 4096) {
174
-        if (!Flac.isReady()) {
175
-            throw new Error('libflac is not ready yet!');
176
-        }
177
-
178
-        this._sampleRate = sampleRate;
179
-        this._bitDepth = bitDepth;
180
-        this._bufferSize = bufferSize;
181
-
182
-        // create the encoder
183
-        this._encoderId = Flac.init_libflac_encoder(
184
-            this._sampleRate,
185
-
186
-            // Mono channel
187
-            1,
188
-            this._bitDepth,
189
-
190
-            FLAC_COMPRESSION_LEVEL,
191
-
192
-            // Pass 0 in because of unknown total samples,
193
-            0,
194
-
195
-            // checksum, FIXME: double-check whether this is necessary
196
-            true,
197
-
198
-            // Auto-determine block size (samples per frame)
199
-            0
200
-        );
201
-
202
-        if (this._encoderId === 0) {
203
-            throw new Error('Failed to create libflac encoder.');
204
-        }
205
-
206
-        // initialize the encoder
207
-        const initResult = Flac.init_encoder_stream(
208
-            this._encoderId,
209
-            this._onEncodedData.bind(this),
210
-            this._onMetadataAvailable.bind(this)
211
-        );
212
-
213
-        if (initResult !== 0) {
214
-            throw new Error('Failed to initialise libflac encoder.');
215
-        }
216
-
217
-        this._state = EncoderState.WORKING;
218
-    }
219
-
220
-    /**
221
-     * Receive and encode new data.
222
-     *
223
-     * @param {Float32Array} audioData - Raw audio data.
224
-     * @returns {void}
225
-     */
226
-    encode(audioData) {
227
-        if (this._state !== EncoderState.WORKING) {
228
-            throw new Error('Encoder is not ready or has finished.');
229
-        }
230
-
231
-        if (!Flac.isReady()) {
232
-            throw new Error('Flac not ready');
233
-        }
234
-        const bufferLength = audioData.length;
235
-
236
-        // Convert sample to signed 32-bit integers.
237
-        // According to libflac documentation:
238
-        // each sample in the buffers should be a signed integer,
239
-        // right-justified to the resolution set by
240
-        // FLAC__stream_encoder_set_bits_per_sample().
241
-
242
-        // Here we are using 16 bits per sample, the samples should all be in
243
-        // the range [-32768,32767]. This is achieved by multipling Float32
244
-        // numbers with 0x7FFF.
245
-
246
-        const bufferI32 = new Int32Array(bufferLength);
247
-        const view = new DataView(bufferI32.buffer);
248
-        const volume = 1;
249
-        let index = 0;
250
-
251
-        for (let i = 0; i < bufferLength; i++) {
252
-            view.setInt32(index, audioData[i] * (0x7FFF * volume), true);
253
-            index += 4; // 4 bytes (32-bit)
254
-        }
255
-
256
-        // pass it to libflac
257
-        const status = Flac.FLAC__stream_encoder_process_interleaved(
258
-            this._encoderId,
259
-            bufferI32,
260
-            bufferI32.length
261
-        );
262
-
263
-        if (status !== 1) {
264
-            // gets error number
265
-
266
-            const errorNo
267
-                = Flac.FLAC__stream_encoder_get_state(this._encoderId);
268
-
269
-            console.error('Error during encoding', FLAC_ERRORS[errorNo]);
270
-        }
271
-    }
272
-
273
-    /**
274
-     * Signals the termination of encoding.
275
-     *
276
-     * @returns {void}
277
-     */
278
-    finish() {
279
-        if (this._state === EncoderState.WORKING) {
280
-            this._state = EncoderState.FINISHED;
281
-
282
-            const status = Flac.FLAC__stream_encoder_finish(this._encoderId);
283
-
284
-            console.log('Flac encoding finished: ', status);
285
-
286
-            // free up resources
287
-            Flac.FLAC__stream_encoder_delete(this._encoderId);
288
-
289
-            this._data = this._exportFlacBlob();
290
-        }
291
-    }
292
-
293
-    /**
294
-     * Gets the encoded flac file.
295
-     *
296
-     * @returns {Blob} - The encoded flac file.
297
-     */
298
-    getBlob() {
299
-        if (this._state === EncoderState.FINISHED) {
300
-            return this._data;
301
-        }
302
-
303
-        return null;
304
-    }
305
-
306
-    /**
307
-     * Converts flac buffer to a Blob.
308
-     *
309
-     * @private
310
-     * @returns {void}
311
-     */
312
-    _exportFlacBlob() {
313
-        const samples = mergeUint8Arrays(this._flacBuffers, this._flacLength);
314
-
315
-        const blob = new Blob([ samples ], { type: 'audio/flac' });
316
-
317
-        return blob;
318
-    }
319
-
320
-    /* eslint-disable no-unused-vars */
321
-    /**
322
-     * Callback function for saving encoded Flac data.
323
-     * This is invoked by libflac.
324
-     *
325
-     * @private
326
-     * @param {Uint8Array} buffer - The encoded Flac data.
327
-     * @param {number} bytes - Number of bytes in the data.
328
-     * @returns {void}
329
-     */
330
-    _onEncodedData(buffer, bytes) {
331
-        this._flacBuffers.push(buffer);
332
-        this._flacLength += buffer.byteLength;
333
-    }
334
-    /* eslint-enable no-unused-vars */
335
-
336
-    /**
337
-     * Callback function for receiving metadata.
338
-     *
339
-     * @private
340
-     * @returns {void}
341
-     */
342
-    _onMetadataAvailable = () => {
343
-        // reserved for future use
344
-    };
345
-}
346
-
347
-
348
-let encoder = null;
349
-
350
-self.onmessage = function(e) {
351
-
352
-    switch (e.data.command) {
353
-    case MAIN_THREAD_INIT:
354
-    {
355
-        const bps = e.data.config.bps;
356
-        const sampleRate = e.data.config.sampleRate;
357
-
358
-        if (Flac.isReady()) {
359
-            encoder = new Encoder(sampleRate, bps);
360
-            self.postMessage({
361
-                command: WORKER_LIBFLAC_READY
362
-            });
363
-        } else {
364
-            Flac.onready = function() {
365
-                setTimeout(() => {
366
-                    encoder = new Encoder(sampleRate, bps);
367
-                    self.postMessage({
368
-                        command: WORKER_LIBFLAC_READY
369
-                    });
370
-                }, 0);
371
-            };
372
-        }
373
-        break;
374
-    }
375
-
376
-    case MAIN_THREAD_NEW_DATA_ARRIVED:
377
-        if (encoder === null) {
378
-            console.error('flacEncoderWorker received data when the encoder is not ready.');
379
-        } else {
380
-            encoder.encode(e.data.buf);
381
-        }
382
-        break;
383
-
384
-    case MAIN_THREAD_FINISH:
385
-        if (encoder !== null) {
386
-            encoder.finish();
387
-            const data = encoder.getBlob();
388
-
389
-            self.postMessage(
390
-                {
391
-                    command: WORKER_BLOB_READY,
392
-                    buf: data
393
-                }
394
-            );
395
-            encoder = null;
396
-        }
397
-        break;
398
-    }
399
-};

+ 0
- 1
react/features/local-recording/recording/flac/index.js Zobrazit soubor

@@ -1 +0,0 @@
1
-export * from './FlacAdapter';

+ 0
- 44
react/features/local-recording/recording/flac/messageTypes.js Zobrazit soubor

@@ -1,44 +0,0 @@
1
-/**
2
- * Types of messages that are passed between the main thread and the WebWorker
3
- * ({@code flacEncodeWorker}).
4
- */
5
-
6
-// Messages sent by the main thread
7
-
8
-/**
9
- * Message type that signals the termination of encoding,
10
- * after which no new audio bits should be sent to the
11
- * WebWorker.
12
- */
13
-export const MAIN_THREAD_FINISH = 'MAIN_THREAD_FINISH';
14
-
15
-/**
16
- * Message type that carries initial parameters for
17
- * the WebWorker.
18
- */
19
-export const MAIN_THREAD_INIT = 'MAIN_THREAD_INIT';
20
-
21
-/**
22
- * Message type that carries the newly received raw audio bits
23
- * for the WebWorker to encode.
24
- */
25
-export const MAIN_THREAD_NEW_DATA_ARRIVED = 'MAIN_THREAD_NEW_DATA_ARRIVED';
26
-
27
-// Messages sent by the WebWorker
28
-
29
-/**
30
- * Message type that signals libflac is ready to receive audio bits.
31
- */
32
-export const WORKER_LIBFLAC_READY = 'WORKER_LIBFLAC_READY';
33
-
34
-/**
35
- * Message type that carries the encoded FLAC file as a Blob.
36
- */
37
-export const WORKER_BLOB_READY = 'WORKER_BLOB_READY';
38
-
39
-// Messages sent by either the main thread or the WebWorker
40
-
41
-/**
42
- * Debug messages.
43
- */
44
-export const DEBUG = 'DEBUG';

+ 0
- 5
react/features/local-recording/recording/index.js Zobrazit soubor

@@ -1,5 +0,0 @@
1
-export * from './OggAdapter';
2
-export * from './RecordingAdapter';
3
-export * from './Utils';
4
-export * from './WavAdapter';
5
-export * from './flac';

+ 0
- 36
react/features/local-recording/reducer.js Zobrazit soubor

@@ -1,36 +0,0 @@
1
-/* @flow */
2
-
3
-import { ReducerRegistry } from '../base/redux';
4
-
5
-import {
6
-    LOCAL_RECORDING_ENGAGED,
7
-    LOCAL_RECORDING_STATS_UPDATE,
8
-    LOCAL_RECORDING_UNENGAGED
9
-} from './actionTypes';
10
-import { recordingController } from './controller';
11
-
12
-ReducerRegistry.register('features/local-recording', (state = {}, action) => {
13
-    switch (action.type) {
14
-    case LOCAL_RECORDING_ENGAGED: {
15
-        return {
16
-            ...state,
17
-            isEngaged: true,
18
-            recordingEngagedAt: action.recordingEngagedAt,
19
-            encodingFormat: recordingController._format
20
-        };
21
-    }
22
-    case LOCAL_RECORDING_UNENGAGED:
23
-        return {
24
-            ...state,
25
-            isEngaged: false,
26
-            recordingEngagedAt: null
27
-        };
28
-    case LOCAL_RECORDING_STATS_UPDATE:
29
-        return {
30
-            ...state,
31
-            stats: action.stats
32
-        };
33
-    default:
34
-        return state;
35
-    }
36
-});

+ 0
- 439
react/features/local-recording/session/SessionManager.js Zobrazit soubor

@@ -1,439 +0,0 @@
1
-/* @flow */
2
-
3
-import Bourne from '@hapi/bourne';
4
-import { jitsiLocalStorage } from '@jitsi/js-utils';
5
-
6
-import logger from '../logger';
7
-
8
-/**
9
- * Gets high precision system time.
10
- *
11
- * @returns {number}
12
- */
13
-function highPrecisionTime(): number {
14
-    return window.performance
15
-        && window.performance.now
16
-        && window.performance.timing
17
-        && window.performance.timing.navigationStart
18
-        ? window.performance.now() + window.performance.timing.navigationStart
19
-        : Date.now();
20
-}
21
-
22
-// Have to use string literal here, instead of Symbols,
23
-// because these values need to be JSON-serializible.
24
-
25
-/**
26
- * Types of SessionEvents.
27
- */
28
-const SessionEventType = Object.freeze({
29
-    /**
30
-     * Start of local recording session. This is recorded when the
31
-     * {@code RecordingController} receives the signal to start local recording,
32
-     * before the actual adapter is engaged.
33
-     */
34
-    SESSION_STARTED: 'SESSION_STARTED',
35
-
36
-    /**
37
-     * Start of a continuous segment. This is recorded when the adapter is
38
-     * engaged. Can happen multiple times in a local recording session,
39
-     * due to browser reloads or switching of recording device.
40
-     */
41
-    SEGMENT_STARTED: 'SEGMENT_STARTED',
42
-
43
-    /**
44
-     * End of a continuous segment. This is recorded when the adapter unengages.
45
-     */
46
-    SEGMENT_ENDED: 'SEGMENT_ENDED'
47
-});
48
-
49
-/**
50
- * Represents an event during a local recording session.
51
- * The event can be either that the adapter started recording, or stopped
52
- * recording.
53
- */
54
-type SessionEvent = {
55
-
56
-    /**
57
-     * The type of the event.
58
-     * Should be one of the values in {@code SessionEventType}.
59
-     */
60
-    type: string,
61
-
62
-    /**
63
-     * The timestamp of the event.
64
-     */
65
-    timestamp: number
66
-};
67
-
68
-/**
69
- * Representation of the metadata of a segment.
70
- */
71
-type SegmentInfo = {
72
-
73
-    /**
74
-     * The length of gap before this segment, in milliseconds.
75
-     * Mull if unknown.
76
-     */
77
-    gapBefore?: ?number,
78
-
79
-    /**
80
-     * The duration of this segment, in milliseconds.
81
-     * Null if unknown or the segment is not finished.
82
-     */
83
-    duration?: ?number,
84
-
85
-    /**
86
-     * The start time, in milliseconds.
87
-     */
88
-    start?: ?number,
89
-
90
-    /**
91
-     * The end time, in milliseconds.
92
-     * Null if unknown, the segment is not finished, or the recording is
93
-     * interrupted (e.g. Browser reload).
94
-     */
95
-    end?: ?number
96
-};
97
-
98
-/**
99
- * Representation of metadata of a local recording session.
100
- */
101
-type SessionInfo = {
102
-
103
-    /**
104
-     * The session token.
105
-     */
106
-    sessionToken: string,
107
-
108
-    /**
109
-     * The start time of the session.
110
-     */
111
-    start: ?number,
112
-
113
-    /**
114
-     * The recording format.
115
-     */
116
-    format: string,
117
-
118
-    /**
119
-     * Array of segments in the session.
120
-     */
121
-    segments: SegmentInfo[]
122
-}
123
-
124
-/**
125
- * {@code localStorage} Key.
126
- */
127
-const LOCAL_STORAGE_KEY = 'localRecordingMetadataVersion1';
128
-
129
-/**
130
- * SessionManager manages the metadata of each segment during each local
131
- * recording session.
132
- *
133
- * A segment is a continuous portion of recording done using the same adapter
134
- * on the same microphone device.
135
- *
136
- * Browser refreshes, switching of microphone will cause new segments to be
137
- * created.
138
- *
139
- * A recording session can consist of one or more segments.
140
- */
141
-class SessionManager {
142
-
143
-    /**
144
-     * The metadata.
145
-     */
146
-    _sessionsMetadata = {
147
-    };
148
-
149
-    /**
150
-     * Constructor.
151
-     */
152
-    constructor() {
153
-        this._loadMetadata();
154
-    }
155
-
156
-    /**
157
-     * Loads metadata from localStorage.
158
-     *
159
-     * @private
160
-     * @returns {void}
161
-     */
162
-    _loadMetadata() {
163
-        const dataStr = jitsiLocalStorage.getItem(LOCAL_STORAGE_KEY);
164
-
165
-        if (dataStr !== null) {
166
-            try {
167
-                const dataObject = Bourne.parse(dataStr);
168
-
169
-                this._sessionsMetadata = dataObject;
170
-            } catch (e) {
171
-                logger.warn('Failed to parse localStorage item.');
172
-
173
-                return;
174
-            }
175
-        }
176
-    }
177
-
178
-    /**
179
-     * Persists metadata to localStorage.
180
-     *
181
-     * @private
182
-     * @returns {void}
183
-     */
184
-    _saveMetadata() {
185
-        jitsiLocalStorage.setItem(LOCAL_STORAGE_KEY,
186
-            JSON.stringify(this._sessionsMetadata));
187
-    }
188
-
189
-    /**
190
-     * Creates a session if not exists.
191
-     *
192
-     * @param {string} sessionToken - The local recording session token.
193
-     * @param {string} format - The local recording format.
194
-     * @returns {void}
195
-     */
196
-    createSession(sessionToken: string, format: string) {
197
-        if (this._sessionsMetadata[sessionToken] === undefined) {
198
-            this._sessionsMetadata[sessionToken] = {
199
-                format,
200
-                events: []
201
-            };
202
-            this._sessionsMetadata[sessionToken].events.push({
203
-                type: SessionEventType.SESSION_STARTED,
204
-                timestamp: highPrecisionTime()
205
-            });
206
-            this._saveMetadata();
207
-        } else {
208
-            logger.warn(`Session ${sessionToken} already exists`);
209
-        }
210
-    }
211
-
212
-    /**
213
-     * Gets all the Sessions.
214
-     *
215
-     * @returns {SessionInfo[]}
216
-     */
217
-    getSessions(): SessionInfo[] {
218
-        const sessionTokens = Object.keys(this._sessionsMetadata);
219
-        const output = [];
220
-
221
-        for (let i = 0; i < sessionTokens.length; ++i) {
222
-            const thisSession = this._sessionsMetadata[sessionTokens[i]];
223
-            const newSessionInfo: SessionInfo = {
224
-                start: thisSession.events[0].timestamp,
225
-                format: thisSession.format,
226
-                sessionToken: sessionTokens[i],
227
-                segments: this.getSegments(sessionTokens[i])
228
-            };
229
-
230
-            output.push(newSessionInfo);
231
-        }
232
-
233
-        output.sort((a, b) => (a.start || 0) - (b.start || 0));
234
-
235
-        return output;
236
-    }
237
-
238
-    /**
239
-     * Removes session metadata.
240
-     *
241
-     * @param {string} sessionToken - The session token.
242
-     * @returns {void}
243
-     */
244
-    removeSession(sessionToken: string) {
245
-        delete this._sessionsMetadata[sessionToken];
246
-        this._saveMetadata();
247
-    }
248
-
249
-    /**
250
-     * Get segments of a given Session.
251
-     *
252
-     * @param {string} sessionToken - The session token.
253
-     * @returns {SegmentInfo[]}
254
-     */
255
-    getSegments(sessionToken: string): SegmentInfo[] {
256
-        const thisSession = this._sessionsMetadata[sessionToken];
257
-
258
-        if (thisSession) {
259
-            return this._constructSegments(thisSession.events);
260
-        }
261
-
262
-        return [];
263
-    }
264
-
265
-    /**
266
-     * Marks the start of a new segment.
267
-     * This should be invoked by {@code RecordingAdapter}s when they need to
268
-     * start asynchronous operations (such as switching tracks) that interrupts
269
-     * recording.
270
-     *
271
-     * @param {string} sessionToken - The token of the session to start a new
272
-     * segment in.
273
-     * @returns {number} - Current segment index.
274
-     */
275
-    beginSegment(sessionToken: string): number {
276
-        if (this._sessionsMetadata[sessionToken] === undefined) {
277
-            logger.warn('Attempting to add segments to nonexistent'
278
-                + ` session ${sessionToken}`);
279
-
280
-            return -1;
281
-        }
282
-        this._sessionsMetadata[sessionToken].events.push({
283
-            type: SessionEventType.SEGMENT_STARTED,
284
-            timestamp: highPrecisionTime()
285
-        });
286
-        this._saveMetadata();
287
-
288
-        return this.getSegments(sessionToken).length - 1;
289
-    }
290
-
291
-    /**
292
-     * Gets the current segment index. Starting from 0 for the first
293
-     * segment.
294
-     *
295
-     * @param {string} sessionToken - The session token.
296
-     * @returns {number}
297
-     */
298
-    getCurrentSegmentIndex(sessionToken: string): number {
299
-        if (this._sessionsMetadata[sessionToken] === undefined) {
300
-            return -1;
301
-        }
302
-        const segments = this.getSegments(sessionToken);
303
-
304
-        if (segments.length === 0) {
305
-            return -1;
306
-        }
307
-
308
-        const lastSegment = segments[segments.length - 1];
309
-
310
-        if (lastSegment.end) {
311
-            // last segment is already ended
312
-            return -1;
313
-        }
314
-
315
-        return segments.length - 1;
316
-    }
317
-
318
-    /**
319
-     * Marks the end of the last segment in a session.
320
-     *
321
-     * @param {string} sessionToken - The session token.
322
-     * @returns {void}
323
-     */
324
-    endSegment(sessionToken: string) {
325
-        if (this._sessionsMetadata[sessionToken] === undefined) {
326
-            logger.warn('Attempting to end a segment in nonexistent'
327
-                + ` session ${sessionToken}`);
328
-        } else {
329
-            this._sessionsMetadata[sessionToken].events.push({
330
-                type: SessionEventType.SEGMENT_ENDED,
331
-                timestamp: highPrecisionTime()
332
-            });
333
-            this._saveMetadata();
334
-        }
335
-    }
336
-
337
-    /**
338
-     * Constructs an array of {@code SegmentInfo} from an array of
339
-     * {@code SessionEvent}s.
340
-     *
341
-     * @private
342
-     * @param {SessionEvent[]} events - The array of {@code SessionEvent}s.
343
-     * @returns {SegmentInfo[]}
344
-     */
345
-    _constructSegments(events: SessionEvent[]): SegmentInfo[] {
346
-        if (events.length === 0) {
347
-            return [];
348
-        }
349
-
350
-        const output = [];
351
-        let sessionStartTime = null;
352
-        let currentSegment: SegmentInfo = {};
353
-
354
-        /**
355
-         * Helper function for adding a new {@code SegmentInfo} object to the
356
-         * output.
357
-         *
358
-         * @returns {void}
359
-         */
360
-        function commit() {
361
-            if (currentSegment.gapBefore === undefined
362
-                || currentSegment.gapBefore === null) {
363
-                if (output.length > 0 && output[output.length - 1].end) {
364
-                    const lastSegment = output[output.length - 1];
365
-
366
-                    if (currentSegment.start && lastSegment.end) {
367
-                        currentSegment.gapBefore = currentSegment.start
368
-                            - lastSegment.end;
369
-                    } else {
370
-                        currentSegment.gapBefore = null;
371
-                    }
372
-                } else if (sessionStartTime !== null && output.length === 0) {
373
-                    currentSegment.gapBefore = currentSegment.start
374
-                        ? currentSegment.start - sessionStartTime
375
-                        : null;
376
-                } else {
377
-                    currentSegment.gapBefore = null;
378
-                }
379
-            }
380
-            currentSegment.duration = currentSegment.end && currentSegment.start
381
-                ? currentSegment.end - currentSegment.start
382
-                : null;
383
-            output.push(currentSegment);
384
-            currentSegment = {};
385
-        }
386
-
387
-        for (let i = 0; i < events.length; ++i) {
388
-            const currentEvent = events[i];
389
-
390
-            switch (currentEvent.type) {
391
-            case SessionEventType.SESSION_STARTED:
392
-                if (sessionStartTime === null) {
393
-                    sessionStartTime = currentEvent.timestamp;
394
-                } else {
395
-                    logger.warn('Unexpected SESSION_STARTED event.'
396
-                        , currentEvent);
397
-                }
398
-                break;
399
-            case SessionEventType.SEGMENT_STARTED:
400
-                if (currentSegment.start === undefined
401
-                    || currentSegment.start === null) {
402
-                    currentSegment.start = currentEvent.timestamp;
403
-                } else {
404
-                    commit();
405
-                    currentSegment.start = currentEvent.timestamp;
406
-                }
407
-                break;
408
-
409
-            case SessionEventType.SEGMENT_ENDED:
410
-                if (currentSegment.start === undefined
411
-                    || currentSegment.start === null) {
412
-                    logger.warn('Unexpected SEGMENT_ENDED event', currentEvent);
413
-                } else {
414
-                    currentSegment.end = currentEvent.timestamp;
415
-                    commit();
416
-                }
417
-                break;
418
-
419
-            default:
420
-                logger.warn('Unexpected error during _constructSegments');
421
-                break;
422
-            }
423
-        }
424
-        if (currentSegment.start) {
425
-            commit();
426
-        }
427
-
428
-        return output;
429
-    }
430
-
431
-}
432
-
433
-/**
434
- * Global singleton of {@code SessionManager}.
435
- */
436
-export const sessionManager = new SessionManager();
437
-
438
-// For debug only. To remove later.
439
-window.sessionManager = sessionManager;

+ 0
- 1
react/features/local-recording/session/index.js Zobrazit soubor

@@ -1 +0,0 @@
1
-export * from './SessionManager';

+ 0
- 8
react/features/toolbox/components/web/Toolbox.js Zobrazit soubor

@@ -36,7 +36,6 @@ import { isGifEnabled } from '../../../gifs/functions';
36 36
 import { InviteButton } from '../../../invite/components/add-people-dialog';
37 37
 import { isVpaasMeeting } from '../../../jaas/functions';
38 38
 import { KeyboardShortcutsButton } from '../../../keyboard-shortcuts';
39
-import { LocalRecordingButton } from '../../../local-recording';
40 39
 import {
41 40
     close as closeParticipantsPane,
42 41
     open as openParticipantsPane
@@ -732,12 +731,6 @@ class Toolbox extends Component<Props> {
732 731
             group: 2
733 732
         };
734 733
 
735
-        const localRecording = {
736
-            key: 'localrecording',
737
-            Content: LocalRecordingButton,
738
-            group: 2
739
-        };
740
-
741 734
         const livestreaming = {
742 735
             key: 'livestreaming',
743 736
             Content: LiveStreamButton,
@@ -844,7 +837,6 @@ class Toolbox extends Component<Props> {
844 837
             security,
845 838
             cc,
846 839
             recording,
847
-            localRecording,
848 840
             livestreaming,
849 841
             linkToSalesforce,
850 842
             muteEveryone,

+ 0
- 10
webpack.config.js Zobrazit soubor

@@ -347,16 +347,6 @@ module.exports = (_env, argv) => {
347 347
             ],
348 348
             performance: getPerformanceHints(perfHintOptions, 5 * 1024)
349 349
         }),
350
-        Object.assign({}, config, {
351
-            entry: {
352
-                'flacEncodeWorker': './react/features/local-recording/recording/flac/flacEncodeWorker.js'
353
-            },
354
-            plugins: [
355
-                ...config.plugins,
356
-                ...getBundleAnalyzerPlugin(analyzeBundle, 'flacEncodeWorker')
357
-            ],
358
-            performance: getPerformanceHints(perfHintOptions, 5 * 1024)
359
-        }),
360 350
         Object.assign({}, config, {
361 351
             entry: {
362 352
                 'analytics-ga': './react/features/analytics/handlers/GoogleAnalyticsHandler.js'

Načítá se…
Zrušit
Uložit