浏览代码

feat: add join/leave sounds on mobile

Adds base/sounds feature which allows other features to register a sound
source under specified id. A new SoundsCollection component will then
render corresponding HTMLAudioElement for each such sound. Once "setRef"
callback is called by the HTMLAudioElement, this element will be added
to the Redux store. When that happens sound can be played through the
new 'playSound' action which will call play() method on the stored
HTMLAudioElement instance.
master
paweldomas 7 年前
父节点
当前提交
60e03e3dec

+ 1
- 0
android/sdk/build.gradle 查看文件

29
     compile project(':react-native-immersive')
29
     compile project(':react-native-immersive')
30
     compile project(':react-native-keep-awake')
30
     compile project(':react-native-keep-awake')
31
     compile project(':react-native-locale-detector')
31
     compile project(':react-native-locale-detector')
32
+    compile project(':react-native-sound')
32
     compile project(':react-native-vector-icons')
33
     compile project(':react-native-vector-icons')
33
     compile project(':react-native-webrtc')
34
     compile project(':react-native-webrtc')
34
 }
35
 }

+ 1
- 0
android/sdk/src/main/java/org/jitsi/meet/sdk/JitsiMeetView.java 查看文件

122
                 .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
122
                 .addPackage(new com.oney.WebRTCModule.WebRTCModulePackage())
123
                 .addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
123
                 .addPackage(new com.RNFetchBlob.RNFetchBlobPackage())
124
                 .addPackage(new com.rnimmersive.RNImmersivePackage())
124
                 .addPackage(new com.rnimmersive.RNImmersivePackage())
125
+                .addPackage(new com.zmxv.RNSound.RNSoundPackage())
125
                 .addPackage(new ReactPackageAdapter() {
126
                 .addPackage(new ReactPackageAdapter() {
126
                     @Override
127
                     @Override
127
                     public List<NativeModule> createNativeModules(
128
                     public List<NativeModule> createNativeModules(

+ 2
- 0
android/settings.gradle 查看文件

11
 project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
11
 project(':react-native-keep-awake').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-keep-awake/android')
12
 include ':react-native-locale-detector'
12
 include ':react-native-locale-detector'
13
 project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
13
 project(':react-native-locale-detector').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-locale-detector/android')
14
+include ':react-native-sound'
15
+project(':react-native-sound').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-sound/android')
14
 include ':react-native-vector-icons'
16
 include ':react-native-vector-icons'
15
 project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
17
 project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
16
 include ':react-native-webrtc'
18
 include ':react-native-webrtc'

+ 1
- 0
ios/Podfile 查看文件

28
   pod 'react-native-locale-detector',
28
   pod 'react-native-locale-detector',
29
     :path => '../node_modules/react-native-locale-detector'
29
     :path => '../node_modules/react-native-locale-detector'
30
   pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
30
   pod 'react-native-webrtc', :path => '../node_modules/react-native-webrtc'
31
+  pod 'RNSound', :path => '../node_modules/react-native-sound'
31
   pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
32
   pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
32
 end
33
 end
33
 
34
 

+ 10
- 1
ios/Podfile.lock 查看文件

41
     - React/Core
41
     - React/Core
42
     - React/fishhook
42
     - React/fishhook
43
     - React/RCTBlob
43
     - React/RCTBlob
44
+  - RNSound (0.10.4):
45
+    - React/Core
46
+    - RNSound/Core (= 0.10.4)
47
+  - RNSound/Core (0.10.4):
48
+    - React/Core
44
   - RNVectorIcons (4.4.2):
49
   - RNVectorIcons (4.4.2):
45
     - React
50
     - React
46
   - yoga (0.51.0.React)
51
   - yoga (0.51.0.React)
61
   - React/RCTNetwork (from `../node_modules/react-native`)
66
   - React/RCTNetwork (from `../node_modules/react-native`)
62
   - React/RCTText (from `../node_modules/react-native`)
67
   - React/RCTText (from `../node_modules/react-native`)
63
   - React/RCTWebSocket (from `../node_modules/react-native`)
68
   - React/RCTWebSocket (from `../node_modules/react-native`)
69
+  - RNSound (from `../node_modules/react-native-sound`)
64
   - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
70
   - RNVectorIcons (from `../node_modules/react-native-vector-icons`)
65
   - yoga (from `../node_modules/react-native/ReactCommon/yoga`)
71
   - yoga (from `../node_modules/react-native/ReactCommon/yoga`)
66
 
72
 
77
     :path: ../node_modules/react-native-locale-detector
83
     :path: ../node_modules/react-native-locale-detector
78
   react-native-webrtc:
84
   react-native-webrtc:
79
     :path: ../node_modules/react-native-webrtc
85
     :path: ../node_modules/react-native-webrtc
86
+  RNSound:
87
+    :path: ../node_modules/react-native-sound
80
   RNVectorIcons:
88
   RNVectorIcons:
81
     :path: ../node_modules/react-native-vector-icons
89
     :path: ../node_modules/react-native-vector-icons
82
   yoga:
90
   yoga:
89
   react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
97
   react-native-keep-awake: 0de4bd66de0c23178107dce0c2fcc3354b2a8e94
90
   react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
98
   react-native-locale-detector: d1b2c6fe5abb56e3a1efb6c2d6f308c05c4251f1
91
   react-native-webrtc: bc044ca9530fc802e7533f247aa08fe1b6bf8dc5
99
   react-native-webrtc: bc044ca9530fc802e7533f247aa08fe1b6bf8dc5
100
+  RNSound: d0818fe2435254fe30540fae48a429c5ffb72e09
92
   RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
101
   RNVectorIcons: c0dbfbf6068fefa240c37b0f71bd03b45dddac44
93
   yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
102
   yoga: 17521bbb0dd54a47c0b3ac43253e78cdac7488e0
94
 
103
 
95
-PODFILE CHECKSUM: fabd6b6c27f8e1849f0668db3f403bf536ac8903
104
+PODFILE CHECKSUM: 1e6ce4da1b385720c726f3f131a6aaf08bf9c0ba
96
 
105
 
97
 COCOAPODS: 1.4.0
106
 COCOAPODS: 1.4.0

+ 0
- 10
modules/UI/UI.js 查看文件

503
         APP.store.dispatch(showParticipantJoinedNotification(displayName));
503
         APP.store.dispatch(showParticipantJoinedNotification(displayName));
504
     }
504
     }
505
 
505
 
506
-    if (!config.startAudioMuted
507
-        || config.startAudioMuted > APP.conference.membersCount) {
508
-        UIUtil.playSoundNotification('userJoined');
509
-    }
510
-
511
     // Add Peer's container
506
     // Add Peer's container
512
     VideoLayout.addParticipantContainer(user);
507
     VideoLayout.addParticipantContainer(user);
513
 
508
 
529
     messageHandler.participantNotification(
524
     messageHandler.participantNotification(
530
         displayName, 'notify.somebody', 'disconnected', 'notify.disconnected');
525
         displayName, 'notify.somebody', 'disconnected', 'notify.disconnected');
531
 
526
 
532
-    if (!config.startAudioMuted
533
-            || config.startAudioMuted > APP.conference.membersCount) {
534
-        UIUtil.playSoundNotification('userLeft');
535
-    }
536
-
537
     VideoLayout.removeParticipantContainer(id);
527
     VideoLayout.removeParticipantContainer(id);
538
 };
528
 };
539
 
529
 

+ 5
- 0
package-lock.json 查看文件

9905
       "resolved": "https://registry.npmjs.org/react-native-prompt/-/react-native-prompt-1.0.0.tgz",
9905
       "resolved": "https://registry.npmjs.org/react-native-prompt/-/react-native-prompt-1.0.0.tgz",
9906
       "integrity": "sha1-QeDsKqfdjxLzo+6Dr51jxLZw+KE="
9906
       "integrity": "sha1-QeDsKqfdjxLzo+6Dr51jxLZw+KE="
9907
     },
9907
     },
9908
+    "react-native-sound": {
9909
+      "version": "0.10.4",
9910
+      "resolved": "https://registry.npmjs.org/react-native-sound/-/react-native-sound-0.10.4.tgz",
9911
+      "integrity": "sha512-V9v4CjKgv8ekQRLOJSoKA7pxJ03F4Ih3T/RfMIlMWLktz7v/O4sdJPjRBLOzZRqAnr9FWTLbSk1ZCjioXh3mjQ=="
9912
+    },
9908
     "react-native-vector-icons": {
9913
     "react-native-vector-icons": {
9909
       "version": "4.4.2",
9914
       "version": "4.4.2",
9910
       "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-4.4.2.tgz",
9915
       "resolved": "https://registry.npmjs.org/react-native-vector-icons/-/react-native-vector-icons-4.4.2.tgz",

+ 1
- 0
package.json 查看文件

62
     "react-native-keep-awake": "2.0.6",
62
     "react-native-keep-awake": "2.0.6",
63
     "react-native-locale-detector": "github:jitsi/react-native-locale-detector#cc76092fc4335488a28a9529c8b50afae2c3ecdc",
63
     "react-native-locale-detector": "github:jitsi/react-native-locale-detector#cc76092fc4335488a28a9529c8b50afae2c3ecdc",
64
     "react-native-prompt": "1.0.0",
64
     "react-native-prompt": "1.0.0",
65
+    "react-native-sound": "0.10.4",
65
     "react-native-vector-icons": "4.4.2",
66
     "react-native-vector-icons": "4.4.2",
66
     "react-native-webrtc": "github:jitsi/react-native-webrtc#626818af40384356617f70366133317b6a475171",
67
     "react-native-webrtc": "github:jitsi/react-native-webrtc#626818af40384356617f70366133317b6a475171",
67
     "react-redux": "5.0.6",
68
     "react-redux": "5.0.6",

+ 3
- 1
react/features/app/components/AbstractApp.js 查看文件

15
 import '../../base/profile';
15
 import '../../base/profile';
16
 import { Fragment, RouteRegistry } from '../../base/react';
16
 import { Fragment, RouteRegistry } from '../../base/react';
17
 import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
17
 import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
18
+import { SoundCollection } from '../../base/sounds';
18
 import { PersistenceRegistry } from '../../base/storage';
19
 import { PersistenceRegistry } from '../../base/storage';
19
 import { toURLString } from '../../base/util';
20
 import { toURLString } from '../../base/util';
20
 import { OverlayContainer } from '../../overlay';
21
 import { OverlayContainer } from '../../overlay';
274
                     <Provider store = { this._getStore() }>
275
                     <Provider store = { this._getStore() }>
275
                         <Fragment>
276
                         <Fragment>
276
                             { this._createElement(component) }
277
                             { this._createElement(component) }
278
+                            <SoundCollection />
277
                             <OverlayContainer />
279
                             <OverlayContainer />
278
                         </Fragment>
280
                         </Fragment>
279
                     </Provider>
281
                     </Provider>
501
     /**
503
     /**
502
      * Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
504
      * Navigates this {@code AbstractApp} to (i.e. opens) a specific URL.
503
      *
505
      *
504
-     * @param {string|Object} url - The URL to navigate this {@code AbstractApp}
506
+     * @param {Object|string} url - The URL to navigate this {@code AbstractApp}
505
      * to (i.e. the URL to open).
507
      * to (i.e. the URL to open).
506
      * @protected
508
      * @protected
507
      * @returns {void}
509
      * @returns {void}

+ 1
- 1
react/features/base/jwt/components/CalleeInfo.js 查看文件

254
         if (this.state.renderAudio && this.state.ringing) {
254
         if (this.state.renderAudio && this.state.ringing) {
255
             return (
255
             return (
256
                 <Audio
256
                 <Audio
257
-                    ref = { this._setAudio }
257
+                    setRef = { this._setAudio }
258
                     src = './sounds/ring.ogg' />
258
                     src = './sounds/ring.ogg' />
259
             );
259
             );
260
         }
260
         }

+ 52
- 66
react/features/base/media/components/AbstractAudio.js 查看文件

1
 // @flow
1
 // @flow
2
 
2
 
3
-import PropTypes from 'prop-types';
4
-import React, { Component } from 'react';
3
+import { Component } from 'react';
4
+
5
+/**
6
+ * Describes audio element interface used in the base/media feature for audio
7
+ * playback.
8
+ */
9
+export type AudioElement = {
10
+    play: Function,
11
+    pause: Function
12
+}
13
+
14
+/**
15
+ * {@code AbstractAudio} component's property types.
16
+ */
17
+type Props = {
18
+
19
+    /**
20
+     * A callback which will be called with {@code AbstractAudio} instance once
21
+     * the audio element is loaded.
22
+     */
23
+    setRef: ?Function,
24
+
25
+    /**
26
+     * The URL of a media resource to use in the element.
27
+     *
28
+     * NOTE on react-native sound files are imported through 'require' and then
29
+     * passed as the 'src' parameter which means their type will be 'any'.
30
+     *
31
+     * @type {Object | string}
32
+     */
33
+    src: Object | string,
34
+    stream: Object
35
+}
5
 
36
 
6
 /**
37
 /**
7
  * The React {@link Component} which is similar to Web's
38
  * The React {@link Component} which is similar to Web's
8
  * {@code HTMLAudioElement}.
39
  * {@code HTMLAudioElement}.
9
  */
40
  */
10
-export default class AbstractAudio extends Component<*> {
41
+export default class AbstractAudio extends Component<Props> {
11
     /**
42
     /**
12
-     * The (reference to the) {@link ReactElement} which actually implements
13
-     * this {@code AbstractAudio}.
43
+     * The {@link AudioElement} instance which implements the audio playback
44
+     * functionality.
14
      */
45
      */
15
-    _ref: ?Object;
16
-
17
-    _setRef: Function;
46
+    _audioElementImpl: ?AudioElement;
18
 
47
 
19
     /**
48
     /**
20
-     * {@code AbstractAudio} component's property types.
21
-     *
22
-     * @static
49
+     * {@link setAudioElementImpl} bound to <code>this</code>.
23
      */
50
      */
24
-    static propTypes = {
25
-        /**
26
-         * The URL of a media resource to use in the element.
27
-         *
28
-         * @type {string}
29
-         */
30
-        src: PropTypes.string,
31
-        stream: PropTypes.object
32
-    };
51
+    setAudioElementImpl: Function;
33
 
52
 
34
     /**
53
     /**
35
      * Initializes a new {@code AbstractAudio} instance.
54
      * Initializes a new {@code AbstractAudio} instance.
41
         super(props);
60
         super(props);
42
 
61
 
43
         // Bind event handlers so they are only bound once for every instance.
62
         // Bind event handlers so they are only bound once for every instance.
44
-        this._setRef = this._setRef.bind(this);
63
+        this.setAudioElementImpl = this.setAudioElementImpl.bind(this);
45
     }
64
     }
46
 
65
 
47
     /**
66
     /**
51
      * @returns {void}
70
      * @returns {void}
52
      */
71
      */
53
     pause() {
72
     pause() {
54
-        this._ref && typeof this._ref.pause === 'function' && this._ref.pause();
73
+        this._audioElementImpl && this._audioElementImpl.pause();
55
     }
74
     }
56
 
75
 
57
     /**
76
     /**
61
      * @returns {void}
80
      * @returns {void}
62
      */
81
      */
63
     play() {
82
     play() {
64
-        this._ref && typeof this._ref.play === 'function' && this._ref.play();
83
+        this._audioElementImpl && this._audioElementImpl.play();
65
     }
84
     }
66
 
85
 
67
     /**
86
     /**
68
-     * Renders this {@code AbstractAudio} as a React {@link Component} of a
69
-     * specific type.
87
+     * Set the (reference to the) {@link AudioElement} object which implements
88
+     * the audio playback functionality.
70
      *
89
      *
71
-     * @param {string|ReactClass} type - The type of the React {@code Component}
72
-     * which is to be rendered.
73
-     * @param {Object|undefined} props - The read-only React {@code Component}
74
-     * properties, if any, to render. If {@code undefined}, the props of this
75
-     * instance will be rendered.
90
+     * @param {AudioElement} element - The {@link AudioElement} instance
91
+     * which implements the audio playback functionality.
76
      * @protected
92
      * @protected
77
-     * @returns {ReactElement}
78
-     */
79
-    _render(type, props) {
80
-        const {
81
-            children,
82
-
83
-            /* eslint-disable no-unused-vars */
84
-
85
-            // The following properties are consumed by React itself so they are
86
-            // to not be propagated.
87
-            ref,
88
-
89
-            /* eslint-enable no-unused-vars */
90
-
91
-            ...filteredProps
92
-        } = props || this.props;
93
-
94
-        return (
95
-            React.createElement(
96
-                type,
97
-                {
98
-                    ...filteredProps,
99
-                    ref: this._setRef
100
-                },
101
-                children));
102
-    }
103
-
104
-    /**
105
-     * Set the (reference to the) {@link ReactElement} which actually implements
106
-     * this {@code AbstractAudio}.
107
-     *
108
-     * @param {Object} ref - The (reference to the) {@code ReactElement} which
109
-     * actually implements this {@code AbstractAudio}.
110
-     * @private
111
      * @returns {void}
93
      * @returns {void}
112
      */
94
      */
113
-    _setRef(ref) {
114
-        this._ref = ref;
95
+    setAudioElementImpl(element: ?AudioElement) {
96
+        this._audioElementImpl = element;
97
+
98
+        if (typeof this.props.setRef === 'function') {
99
+            this.props.setRef(element ? this : null);
100
+        }
115
     }
101
     }
116
 }
102
 }

+ 50
- 3
react/features/base/media/components/native/Audio.js 查看文件

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import Sound from 'react-native-sound';
4
+
3
 import AbstractAudio from '../AbstractAudio';
5
 import AbstractAudio from '../AbstractAudio';
4
 
6
 
7
+const logger = require('jitsi-meet-logger').getLogger(__filename);
8
+
5
 /**
9
 /**
6
  * The React Native/mobile {@link Component} which is similar to Web's
10
  * The React Native/mobile {@link Component} which is similar to Web's
7
  * {@code HTMLAudioElement} and wraps around react-native-webrtc's
11
  * {@code HTMLAudioElement} and wraps around react-native-webrtc's
8
  * {@link RTCView}.
12
  * {@link RTCView}.
9
  */
13
  */
10
 export default class Audio extends AbstractAudio {
14
 export default class Audio extends AbstractAudio {
15
+
11
     /**
16
     /**
12
-     * {@code Audio} component's property types.
17
+     * Reference to 'react-native-sound} {@link Sound} instance.
18
+     */
19
+    _sound: Sound
20
+
21
+    /**
22
+     * A callback passed to the 'react-native-sound''s {@link Sound} instance,
23
+     * called when loading sound is finished.
13
      *
24
      *
14
-     * @static
25
+     * @param {Object} error - The error object passed by
26
+     * the 'react-native-sound' library.
27
+     * @returns {void}
28
+     * @private
15
      */
29
      */
16
-    static propTypes = AbstractAudio.propTypes;
30
+    _soundLoadedCallback(error) {
31
+        if (error) {
32
+            logger.error('Failed to load sound', error);
33
+        } else {
34
+            this.setAudioElementImpl(this._sound);
35
+        }
36
+    }
37
+
38
+    /**
39
+     * Will load the sound, after the component did mount.
40
+     *
41
+     * @returns {void}
42
+     */
43
+    componentDidMount() {
44
+        this._sound
45
+            = this.props.src
46
+                ? new Sound(
47
+                    this.props.src,
48
+                    this._soundLoadedCallback.bind(this))
49
+                : null;
50
+    }
51
+
52
+    /**
53
+     * Will dispose sound resources (if any) when component is about to unmount.
54
+     *
55
+     * @returns {void}
56
+     */
57
+    componentWillUnmount() {
58
+        if (this._sound) {
59
+            this.setAudioElementImpl(null);
60
+            this._sound.release();
61
+            this._sound = null;
62
+        }
63
+    }
17
 
64
 
18
     /**
65
     /**
19
      * Implements React's {@link Component#render()}.
66
      * Implements React's {@link Component#render()}.

+ 87
- 4
react/features/base/media/components/web/Audio.js 查看文件

1
 /* @flow */
1
 /* @flow */
2
 
2
 
3
+import React from 'react';
4
+
3
 import AbstractAudio from '../AbstractAudio';
5
 import AbstractAudio from '../AbstractAudio';
4
 
6
 
5
 /**
7
 /**
8
  */
10
  */
9
 export default class Audio extends AbstractAudio {
11
 export default class Audio extends AbstractAudio {
10
     /**
12
     /**
11
-     * {@code Audio} component's property types.
13
+     * Set to <code>true</code> when the whole file is loaded.
14
+     */
15
+    _audioFileLoaded: boolean;
16
+
17
+    /**
18
+     * {@link _onCanPlayThrough} bound to "this".
19
+     */
20
+    _onCanPlayThrough: Function;
21
+
22
+    /**
23
+     * Reference to the HTML audio element, stored until the file is ready.
24
+     */
25
+    _ref: HTMLAudioElement;
26
+
27
+    /**
28
+     * {@link _setRef} bound to "this".
29
+     */
30
+    _setRef: Function;
31
+
32
+    /**
33
+     * Creates new <code>Audio</code> element instance with given props.
12
      *
34
      *
13
-     * @static
35
+     * @param {Object} props - The read-only properties with which the new
36
+     * instance is to be initialized.
14
      */
37
      */
15
-    static propTypes = AbstractAudio.propTypes;
38
+    constructor(props: Object) {
39
+        super(props);
40
+
41
+        // Bind event handlers so they are only bound once for every instance.
42
+        this._onCanPlayThrough = this._onCanPlayThrough.bind(this);
43
+        this._setRef = this._setRef.bind(this);
44
+    }
16
 
45
 
17
     /**
46
     /**
18
      * Implements React's {@link Component#render()}.
47
      * Implements React's {@link Component#render()}.
21
      * @returns {ReactElement}
50
      * @returns {ReactElement}
22
      */
51
      */
23
     render() {
52
     render() {
24
-        return super._render('audio');
53
+        return (
54
+            <audio
55
+                onCanPlayThrough = { this._onCanPlayThrough }
56
+                preload = 'auto'
57
+                ref = { this._setRef }
58
+                src = { this.props.src } />
59
+        );
60
+    }
61
+
62
+    /**
63
+     * If audio element reference has been set and the file has been
64
+     * loaded then {@link setAudioElementImpl} will be called to eventually add
65
+     * the audio to the Redux store.
66
+     *
67
+     * @private
68
+     * @returns {void}
69
+     */
70
+    _maybeSetAudioElementImpl() {
71
+        if (this._ref && this._audioFileLoaded) {
72
+            this.setAudioElementImpl(this._ref);
73
+        }
74
+    }
75
+
76
+    /**
77
+     * Called when 'canplaythrough' event is triggered on the audio element,
78
+     * which means that the whole file has been loaded.
79
+     *
80
+     * @private
81
+     * @returns {void}
82
+     */
83
+    _onCanPlayThrough() {
84
+        this._audioFileLoaded = true;
85
+        this._maybeSetAudioElementImpl();
86
+    }
87
+
88
+    /**
89
+     * Sets the reference to the HTML audio element.
90
+     *
91
+     * @param {HTMLAudioElement} audioElement - The HTML audio element instance.
92
+     * @private
93
+     * @returns {void}
94
+     */
95
+    _setRef(audioElement: HTMLAudioElement) {
96
+        this._ref = audioElement;
97
+        if (audioElement) {
98
+            this._maybeSetAudioElementImpl();
99
+        } else {
100
+            // AbstractAudioElement is supposed to trigger "removeAudio" only if
101
+            // it was previously added, so it's safe to just call it.
102
+            this.setAudioElementImpl(null);
103
+
104
+            // Reset the loaded flag, as the audio element is being removed from
105
+            // the DOM tree.
106
+            this._audioFileLoaded = false;
107
+        }
25
     }
108
     }
26
 }
109
 }

+ 14
- 0
react/features/base/participants/constants.js 查看文件

28
  */
28
  */
29
 export const MAX_DISPLAY_NAME_LENGTH = 50;
29
 export const MAX_DISPLAY_NAME_LENGTH = 50;
30
 
30
 
31
+/**
32
+ * The identifier of the sound to be played when new remote participant joins
33
+ * the room.
34
+ * @type {string}
35
+ */
36
+export const PARTICIPANT_JOINED_SOUND_ID = 'PARTICIPANT_JOINED_SOUND';
37
+
38
+/**
39
+ * The identifier of the sound to be played when remote participant leaves
40
+ * the room.
41
+ * @type {string}
42
+ */
43
+export const PARTICIPANT_LEFT_SOUND_ID = 'PARTICIPANT_LEFT_SOUND';
44
+
31
 /**
45
 /**
32
  * The set of possible XMPP MUC roles for conference participants.
46
  * The set of possible XMPP MUC roles for conference participants.
33
  *
47
  *

+ 81
- 2
react/features/base/participants/middleware.js 查看文件

2
 
2
 
3
 import UIEvents from '../../../../service/UI/UIEvents';
3
 import UIEvents from '../../../../service/UI/UIEvents';
4
 
4
 
5
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../../app';
5
 import {
6
 import {
6
     CONFERENCE_JOINED,
7
     CONFERENCE_JOINED,
7
     CONFERENCE_LEFT
8
     CONFERENCE_LEFT
8
 } from '../conference';
9
 } from '../conference';
9
 import { MiddlewareRegistry } from '../redux';
10
 import { MiddlewareRegistry } from '../redux';
11
+import { playSound, registerSound, unregisterSound } from '../sounds';
10
 
12
 
11
 import { localParticipantIdChanged } from './actions';
13
 import { localParticipantIdChanged } from './actions';
12
 import {
14
 import {
14
     MUTE_REMOTE_PARTICIPANT,
16
     MUTE_REMOTE_PARTICIPANT,
15
     PARTICIPANT_DISPLAY_NAME_CHANGED,
17
     PARTICIPANT_DISPLAY_NAME_CHANGED,
16
     PARTICIPANT_JOINED,
18
     PARTICIPANT_JOINED,
19
+    PARTICIPANT_LEFT,
17
     PARTICIPANT_UPDATED
20
     PARTICIPANT_UPDATED
18
 } from './actionTypes';
21
 } from './actionTypes';
19
-import { LOCAL_PARTICIPANT_DEFAULT_ID } from './constants';
22
+import {
23
+    LOCAL_PARTICIPANT_DEFAULT_ID,
24
+    PARTICIPANT_JOINED_SOUND_ID,
25
+    PARTICIPANT_LEFT_SOUND_ID
26
+} from './constants';
20
 import {
27
 import {
21
     getAvatarURLByParticipantId,
28
     getAvatarURLByParticipantId,
22
-    getLocalParticipant
29
+    getLocalParticipant,
30
+    getParticipantCount
23
 } from './functions';
31
 } from './functions';
32
+import {
33
+    PARTICIPANT_JOINED_SRC,
34
+    PARTICIPANT_LEFT_SRC
35
+} from './sounds';
24
 
36
 
25
 declare var APP: Object;
37
 declare var APP: Object;
26
 
38
 
34
 MiddlewareRegistry.register(store => next => action => {
46
 MiddlewareRegistry.register(store => next => action => {
35
     const { conference } = store.getState()['features/base/conference'];
47
     const { conference } = store.getState()['features/base/conference'];
36
 
48
 
49
+    if (action.type === PARTICIPANT_JOINED
50
+        || action.type === PARTICIPANT_LEFT) {
51
+        _maybePlaySounds(store, action);
52
+    }
53
+
37
     switch (action.type) {
54
     switch (action.type) {
55
+    case APP_WILL_MOUNT:
56
+        _registerSounds(store);
57
+        break;
58
+    case APP_WILL_UNMOUNT:
59
+        _unregisterSounds(store);
60
+        break;
38
     case CONFERENCE_JOINED:
61
     case CONFERENCE_JOINED:
39
         store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
62
         store.dispatch(localParticipantIdChanged(action.conference.myUserId()));
40
         break;
63
         break;
100
 
123
 
101
     return next(action);
124
     return next(action);
102
 });
125
 });
126
+
127
+/**
128
+ * Plays sounds when participants join/leave conference.
129
+ *
130
+ * @param {Store} store - The Redux store.
131
+ * @param {Action} action - The Redux action. Should be either
132
+ * {@link PARTICIPANT_JOINED} or {@link PARTICIPANT_LEFT}.
133
+ * @private
134
+ * @returns {void}
135
+ */
136
+function _maybePlaySounds({ getState, dispatch }, action) {
137
+    const state = getState();
138
+    const { startAudioMuted } = state['features/base/config'];
139
+
140
+    // We're not playing sounds for local participant
141
+    // nor when the user is joining past the "startAudioMuted" limit.
142
+    // The intention there was to not play user joined notification in big
143
+    // conferences where 100th person is joining.
144
+    if (!action.participant.local
145
+        && (!startAudioMuted
146
+            || getParticipantCount(state) < startAudioMuted)) {
147
+        if (action.type === PARTICIPANT_JOINED) {
148
+            dispatch(playSound(PARTICIPANT_JOINED_SOUND_ID));
149
+        } else if (action.type === PARTICIPANT_LEFT) {
150
+            dispatch(playSound(PARTICIPANT_LEFT_SOUND_ID));
151
+        }
152
+    }
153
+}
154
+
155
+/**
156
+ * Registers sounds related with the participants feature.
157
+ *
158
+ * @param {Store} store - The Redux store.
159
+ * @private
160
+ * @returns {void}
161
+ */
162
+function _registerSounds({ dispatch }) {
163
+    dispatch(
164
+        registerSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_SRC));
165
+    dispatch(
166
+        registerSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_SRC));
167
+}
168
+
169
+/**
170
+ * Unregisters sounds related with the participants feature.
171
+ *
172
+ * @param {Store} store - The Redux store.
173
+ * @private
174
+ * @returns {void}
175
+ */
176
+function _unregisterSounds({ dispatch }) {
177
+    dispatch(
178
+        unregisterSound(PARTICIPANT_JOINED_SOUND_ID, PARTICIPANT_JOINED_SRC));
179
+    dispatch(
180
+        unregisterSound(PARTICIPANT_LEFT_SOUND_ID, PARTICIPANT_LEFT_SRC));
181
+}

+ 13
- 0
react/features/base/participants/sounds.native.js 查看文件

1
+/**
2
+ * Points to the sound file which will be played when new participant joins
3
+ * the conference.
4
+ */
5
+export const PARTICIPANT_JOINED_SRC
6
+    = require('../../../../sounds/joined.wav');
7
+
8
+/**
9
+ * Points to the sound file which will be played when any participant leaves
10
+ * the conference.
11
+ */
12
+export const PARTICIPANT_LEFT_SRC
13
+    = require('../../../../sounds/left.wav');

+ 11
- 0
react/features/base/participants/sounds.web.js 查看文件

1
+/**
2
+ * Points to the sound file which will be played when new participant joins
3
+ * the conference.
4
+ */
5
+export const PARTICIPANT_JOINED_SRC = 'sounds/joined.wav';
6
+
7
+/**
8
+ * Points to the sound file which will be played when any participant leaves
9
+ * the conference.
10
+ */
11
+export const PARTICIPANT_LEFT_SRC = 'sounds/left.wav';

+ 54
- 0
react/features/base/sounds/actionTypes.js 查看文件

1
+/**
2
+ * The type of a feature/internal/protected (redux) action to add an audio
3
+ * element to the sounds collection state.
4
+ *
5
+ * {
6
+ *     type: _ADD_AUDIO_ELEMENT,
7
+ *     ref: AudioElement,
8
+ *     soundId: string
9
+ * }
10
+ */
11
+export const _ADD_AUDIO_ELEMENT = Symbol('_ADD_AUDIO_ELEMENT');
12
+
13
+/**
14
+ * The type of feature/internal/protected (redux) action to remove an audio
15
+ * element for given sound identifier from the sounds collection state.
16
+ *
17
+ * {
18
+ *     type: _REMOVE_AUDIO_ELEMENT,
19
+ *     soundId: string
20
+ * }
21
+ */
22
+export const _REMOVE_AUDIO_ELEMENT = Symbol('_REMOVE_AUDIO_ELEMENT');
23
+
24
+/**
25
+ * The type of (redux) action to play a sound from the sounds collection.
26
+ *
27
+ * {
28
+ *     type: PLAY_SOUND,
29
+ *     soundId: string
30
+ * }
31
+ */
32
+export const PLAY_SOUND = Symbol('PLAY_SOUND');
33
+
34
+/**
35
+ * The type of (redux) action to register a new sound with the sounds
36
+ * collection.
37
+ *
38
+ * {
39
+ *     type: REGISTER_SOUND,
40
+ *     soundId: string
41
+ * }
42
+ */
43
+export const REGISTER_SOUND = Symbol('REGISTER_SOUND');
44
+
45
+/**
46
+ * The type of (redux) action to unregister an existing sound from the sounds
47
+ * collection.
48
+ *
49
+ * {
50
+ *     type: UNREGISTER_SOUND,
51
+ *     soundId: string
52
+ * }
53
+ */
54
+export const UNREGISTER_SOUND = Symbol('UNREGISTER_SOUND');

+ 118
- 0
react/features/base/sounds/actions.js 查看文件

1
+// @flow
2
+
3
+import type { AudioElement } from '../media';
4
+
5
+import {
6
+    _ADD_AUDIO_ELEMENT,
7
+    _REMOVE_AUDIO_ELEMENT,
8
+    PLAY_SOUND,
9
+    REGISTER_SOUND,
10
+    UNREGISTER_SOUND
11
+} from './actionTypes';
12
+
13
+/**
14
+ * Adds {@link AudioElement} instance to the base/sounds feature state for the
15
+ * {@link Sound} instance identified by the given id. After this action the
16
+ * sound can be played by dispatching the {@link PLAY_SOUND} action.
17
+ *
18
+ * @param {string} soundId - The sound identifier for which the audio element
19
+ * will be stored.
20
+ * @param {AudioElement} audioElement - The audio element which implements the
21
+ * audio playback functionality and which is backed by the sound resource
22
+ * corresponding to the {@link Sound} with the given id.
23
+ * @protected
24
+ * @returns {{
25
+ *     type: PLAY_SOUND,
26
+ *     audioElement: AudioElement,
27
+ *     soundId: string
28
+ * }}
29
+ */
30
+export function _addAudioElement(soundId: string, audioElement: AudioElement) {
31
+    return {
32
+        type: _ADD_AUDIO_ELEMENT,
33
+        audioElement,
34
+        soundId
35
+    };
36
+}
37
+
38
+/**
39
+ * The opposite of {@link _addAudioElement} which removes {@link AudioElement}
40
+ * for given sound from base/sounds state. It means that the audio resource has
41
+ * been disposed and the sound can no longer be played.
42
+ *
43
+ * @param {string} soundId - The {@link Sound} instance identifier for which the
44
+ * audio element is being removed.
45
+ * @protected
46
+ * @returns {{
47
+ *     type: _REMOVE_AUDIO_ELEMENT,
48
+ *     soundId: string
49
+ * }}
50
+ */
51
+export function _removeAudioElement(soundId: string) {
52
+    return {
53
+        type: _REMOVE_AUDIO_ELEMENT,
54
+        soundId
55
+    };
56
+}
57
+
58
+/**
59
+ * Starts playback of the sound identified by the given sound id. The action
60
+ * will have effect only if the audio resource has been loaded already.
61
+ *
62
+ * @param {string} soundId - The id of the sound to be played (the same one
63
+ * which was used in {@link registerSound} to register the sound).
64
+ * @returns {{
65
+ *     type: PLAY_SOUND,
66
+ *     soundId: string
67
+ * }}
68
+ */
69
+export function playSound(soundId: string): Object {
70
+    return {
71
+        type: PLAY_SOUND,
72
+        soundId
73
+    };
74
+}
75
+
76
+/**
77
+ * Registers a new sound for given id and a source object which can be either a
78
+ * path or a raw object depending on the platform (native vs web). It will make
79
+ * the {@link SoundCollection} render extra HTMLAudioElement which will make it
80
+ * available for playback through the {@link playSound} action.
81
+ *
82
+ * @param {string} soundId - The global identifier which identify the sound
83
+ * created for given source object.
84
+ * @param {Object|string} src - Either path to an audio file or a raw object
85
+ * which specifies the audio resource that will be associated with the given
86
+ * {@code soundId}.
87
+ * @returns {{
88
+ *     type: REGISTER_SOUND,
89
+ *     soundId: string,
90
+ *     src: (Object | string)
91
+ * }}
92
+ */
93
+export function registerSound(soundId: string, src: Object | string): Object {
94
+    return {
95
+        type: REGISTER_SOUND,
96
+        soundId,
97
+        src
98
+    };
99
+}
100
+
101
+/**
102
+ * Unregister the sound identified by the given id. It will make the
103
+ * {@link SoundCollection} component stop rendering the corresponding
104
+ * {@code HTMLAudioElement} which then should result in the audio resource
105
+ * disposal.
106
+ *
107
+ * @param {string} soundId - The identifier of the {@link Sound} to be removed.
108
+ * @returns {{
109
+ *     type: UNREGISTER_SOUND,
110
+ *     soundId: string
111
+ * }}
112
+ */
113
+export function unregisterSound(soundId: string): Object {
114
+    return {
115
+        type: UNREGISTER_SOUND,
116
+        soundId
117
+    };
118
+}

+ 160
- 0
react/features/base/sounds/components/SoundCollection.js 查看文件

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import { connect } from 'react-redux';
5
+
6
+import { Audio } from '../../media';
7
+import type { AudioElement } from '../../media';
8
+import { Fragment } from '../../react';
9
+
10
+import { _addAudioElement, _removeAudioElement } from '../actions';
11
+import type { Sound } from '../reducer';
12
+
13
+/**
14
+ * {@link SoundCollection}'s properties.
15
+ */
16
+type Props = {
17
+
18
+    /**
19
+     * Dispatches {@link _ADD_AUDIO_ELEMENT} Redux action which will store the
20
+     * {@link AudioElement} for a sound in the Redux store.
21
+     */
22
+    _addAudioElement: Function,
23
+
24
+    /**
25
+     * Dispatches {@link _REMOVE_AUDIO_ELEMENT} Redux action which will remove
26
+     * the sound's {@link AudioElement} from the Redux store.
27
+     */
28
+    _removeAudioElement: Function,
29
+
30
+    /**
31
+     * It's the 'base/sounds' reducer's state mapped to a property. It's used to
32
+     * render audio elements for every registered sound.
33
+     */
34
+    _sounds: Map<string, Sound>
35
+}
36
+
37
+/**
38
+ * Collections of all global sounds used by the app for playing audio
39
+ * notifications in response to various events. It renders <code>Audio</code>
40
+ * element for each sound registered in the base/sounds feature. When the audio
41
+ * resource is loaded it will emit add/remove audio element actions which will
42
+ * attach the element to the corresponding {@link Sound} instance in the Redux
43
+ * state. When that happens the sound can be played using the {@link playSound}
44
+ * action.
45
+ */
46
+class SoundCollection extends Component<Props> {
47
+    /**
48
+     * Implements React's {@link Component#render()}.
49
+     *
50
+     * @inheritdoc
51
+     * @returns {ReactElement}
52
+     */
53
+    render() {
54
+        let key = 0;
55
+        const sounds = [];
56
+
57
+        for (const [ soundId, sound ] of this.props._sounds.entries()) {
58
+            sounds.push(
59
+                React.createElement(
60
+                    Audio, {
61
+                        key,
62
+                        setRef: this._setRef.bind(this, soundId),
63
+                        src: sound.src
64
+                    }));
65
+            key += 1;
66
+        }
67
+
68
+        return (
69
+            <Fragment>
70
+                {
71
+                    sounds
72
+                }
73
+            </Fragment>
74
+        );
75
+    }
76
+
77
+    /**
78
+     * Set the (reference to the) {@link AudioElement} object which implements
79
+     * the audio playback functionality.
80
+     *
81
+     * @param {string} soundId - The sound Id for the audio element for which
82
+     * the callback is being executed.
83
+     * @param {AudioElement} element - The {@link AudioElement} instance
84
+     * which implements the audio playback functionality.
85
+     * @protected
86
+     * @returns {void}
87
+     */
88
+    _setRef(soundId: string, element: ?AudioElement) {
89
+        if (element) {
90
+            this.props._addAudioElement(soundId, element);
91
+        } else {
92
+            this.props._removeAudioElement(soundId);
93
+        }
94
+    }
95
+}
96
+
97
+/**
98
+ * Maps (parts of) the Redux state to {@code SoundCollection}'s props.
99
+ *
100
+ * @param {Object} state - The redux state.
101
+ * @private
102
+ * @returns {{
103
+ *     _sounds: Map<string, Sound>
104
+ * }}
105
+ */
106
+function _mapStateToProps(state) {
107
+    return {
108
+        _sounds: state['features/base/sounds']
109
+    };
110
+}
111
+
112
+/**
113
+ * Maps dispatching of some actions to React component props.
114
+ *
115
+ * @param {Function} dispatch - Redux action dispatcher.
116
+ * @private
117
+ * @returns {{
118
+ *     _addAudioElement: void,
119
+ *     _removeAudioElement: void
120
+ * }}
121
+ */
122
+export function _mapDispatchToProps(dispatch: Function) {
123
+    return {
124
+        /**
125
+         * Dispatches action to store the {@link AudioElement} for
126
+         * a {@link Sound} identified by given <tt>soundId</tt> in the Redux
127
+         * store, so that the playback can be controlled through the Redux
128
+         * actions.
129
+         *
130
+         * @param {string} soundId - A global identifier which will be used to
131
+         * identify the {@link Sound} instance for which an audio element will
132
+         * be added.
133
+         * @param {AudioElement} audioElement - The {@link AudioElement}
134
+         * instance that will be stored in the Redux state of the base/sounds
135
+         * feature, as part of the {@link Sound} object. At that point the sound
136
+         * will be ready for playback.
137
+         * @private
138
+         * @returns {void}
139
+         */
140
+        _addAudioElement(soundId: string, audioElement: AudioElement) {
141
+            dispatch(_addAudioElement(soundId, audioElement));
142
+        },
143
+
144
+        /**
145
+         * Dispatches action to remove {@link AudioElement} from the Redux
146
+         * store for specific {@link Sound}, because it is no longer part of
147
+         * the DOM tree and the audio resource will be released.
148
+         *
149
+         * @param {string} soundId - The id of the {@link Sound} instance for
150
+         * which an {@link AudioElement} will be removed from the Redux store.
151
+         * @private
152
+         * @returns {void}
153
+         */
154
+        _removeAudioElement(soundId: string) {
155
+            dispatch(_removeAudioElement(soundId));
156
+        }
157
+    };
158
+}
159
+
160
+export default connect(_mapStateToProps, _mapDispatchToProps)(SoundCollection);

+ 1
- 0
react/features/base/sounds/components/index.js 查看文件

1
+export { default as SoundCollection } from './SoundCollection';

+ 6
- 0
react/features/base/sounds/index.js 查看文件

1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './components';
4
+
5
+import './middleware';
6
+import './reducer';

+ 46
- 0
react/features/base/sounds/middleware.js 查看文件

1
+// @flow
2
+
3
+import { MiddlewareRegistry } from '../redux';
4
+
5
+import { PLAY_SOUND } from './actionTypes';
6
+
7
+const logger = require('jitsi-meet-logger').getLogger(__filename);
8
+
9
+/**
10
+ * Implements the entry point of the middleware of the feature base/media.
11
+ *
12
+ * @param {Store} store - The redux store.
13
+ * @returns {Function}
14
+ */
15
+MiddlewareRegistry.register(store => next => action => {
16
+    switch (action.type) {
17
+    case PLAY_SOUND:
18
+        _playSound(store, action.soundId);
19
+        break;
20
+    }
21
+
22
+    return next(action);
23
+});
24
+
25
+/**
26
+ * Plays sound from audio element registered in the Redux store.
27
+ *
28
+ * @param {Store} store - The Redux store instance.
29
+ * @param {string} soundId - Audio element identifier.
30
+ * @private
31
+ * @returns {void}
32
+ */
33
+function _playSound({ getState }, soundId) {
34
+    const sounds = getState()['features/base/sounds'];
35
+    const sound = sounds.get(soundId);
36
+
37
+    if (sound) {
38
+        if (sound.audioElement) {
39
+            sound.audioElement.play();
40
+        } else {
41
+            logger.warn(`PLAY_SOUND: sound not loaded yet for id: ${soundId}`);
42
+        }
43
+    } else {
44
+        logger.warn(`PLAY_SOUND: no sound found for id: ${soundId}`);
45
+    }
46
+}

+ 140
- 0
react/features/base/sounds/reducer.js 查看文件

1
+// @flow
2
+
3
+import type { AudioElement } from '../media';
4
+import { assign, ReducerRegistry } from '../redux';
5
+
6
+import {
7
+    _ADD_AUDIO_ELEMENT,
8
+    _REMOVE_AUDIO_ELEMENT,
9
+    REGISTER_SOUND,
10
+    UNREGISTER_SOUND
11
+} from './actionTypes';
12
+
13
+const logger = require('jitsi-meet-logger').getLogger(__filename);
14
+
15
+/**
16
+ * The structure use by this reducer to describe a sound.
17
+ */
18
+export type Sound = {
19
+
20
+    /**
21
+     * The HTMLAudioElement which implements the audio playback functionality.
22
+     * Becomes available once the sound resource gets loaded and the sound can
23
+     * not be played until that happens.
24
+     */
25
+    audioElement?: AudioElement,
26
+
27
+    /**
28
+     * This field describes the source of the audio resource to be played. It
29
+     * can be either a path to the file or an object depending on the platform
30
+     * (native vs web).
31
+     */
32
+    src: Object | string
33
+}
34
+
35
+/**
36
+ * Initial/default state of the feature {@code base/sounds}. It is a {@code Map}
37
+ * of globally stored sounds.
38
+ *
39
+ * @type {Map<string, Sound>}
40
+ */
41
+const DEFAULT_STATE = new Map();
42
+
43
+/**
44
+ * The base/sounds feature's reducer.
45
+ */
46
+ReducerRegistry.register(
47
+    'features/base/sounds',
48
+    (state = DEFAULT_STATE, action) => {
49
+        switch (action.type) {
50
+        case _ADD_AUDIO_ELEMENT:
51
+        case _REMOVE_AUDIO_ELEMENT:
52
+            return _addOrRemoveAudioElement(state, action);
53
+
54
+        case REGISTER_SOUND:
55
+            return _registerSound(state, action);
56
+
57
+        case UNREGISTER_SOUND:
58
+            return _unregisterSound(state, action);
59
+
60
+        default:
61
+            return state;
62
+        }
63
+    });
64
+
65
+/**
66
+ * Adds or removes {@link AudioElement} associated with a {@link Sound}.
67
+ *
68
+ * @param {Map<string, Sound>} state - The current Redux state of this feature.
69
+ * @param {_ADD_AUDIO_ELEMENT | _REMOVE_AUDIO_ELEMENT} action - The action to be
70
+ * handled.
71
+ * @private
72
+ * @returns {Map<string, Sound>}
73
+ */
74
+function _addOrRemoveAudioElement(state, action) {
75
+    const isAddAction = action.type === _ADD_AUDIO_ELEMENT;
76
+    const nextState = new Map(state);
77
+    const { soundId } = action;
78
+
79
+    const sound = nextState.get(soundId);
80
+
81
+    if (sound) {
82
+        if (isAddAction) {
83
+            nextState.set(soundId,
84
+                assign(sound, {
85
+                    audioElement: action.audioElement
86
+                }));
87
+        } else {
88
+            nextState.set(soundId,
89
+                assign(sound, {
90
+                    audioElement: undefined
91
+                }));
92
+        }
93
+    } else {
94
+        const actionName
95
+            = isAddAction ? '_ADD_AUDIO_ELEMENT' : '_REMOVE_AUDIO_ELEMENT';
96
+
97
+        logger.error(`${actionName}: no sound for id: ${soundId}`);
98
+    }
99
+
100
+    return nextState;
101
+}
102
+
103
+/**
104
+ * Registers a new {@link Sound} for given id and source. It will make
105
+ * the {@link SoundCollection} component render HTMLAudioElement for given
106
+ * source making it available for playback through the redux actions.
107
+ *
108
+ * @param {Map<string, Sound>} state - The current Redux state of the sounds
109
+ * features.
110
+ * @param {REGISTER_SOUND} action - The register sound action.
111
+ * @private
112
+ * @returns {Map<string, Sound>}
113
+ */
114
+function _registerSound(state, action) {
115
+    const nextState = new Map(state);
116
+
117
+    nextState.set(action.soundId, {
118
+        src: action.src
119
+    });
120
+
121
+    return nextState;
122
+}
123
+
124
+/**
125
+ * Unregisters a {@link Sound} which will make the {@link SoundCollection}
126
+ * component stop rendering the corresponding HTMLAudioElement. This will
127
+ * result further in the audio resource disposal.
128
+ *
129
+ * @param {Map<string, Sound>} state - The current Redux state of this feature.
130
+ * @param {UNREGISTER_SOUND} action - The unregister sound action.
131
+ * @private
132
+ * @returns {Map<string, Sound>}
133
+ */
134
+function _unregisterSound(state, action) {
135
+    const nextState = new Map(state);
136
+
137
+    nextState.delete(action.soundId);
138
+
139
+    return nextState;
140
+}

+ 2
- 2
react/features/base/util/uri.js 查看文件

327
  * the one accepted by the constructor of Web's ExternalAPI is supported on both
327
  * the one accepted by the constructor of Web's ExternalAPI is supported on both
328
  * mobile/React Native and Web/React.
328
  * mobile/React Native and Web/React.
329
  *
329
  *
330
- * @param {string|Object} obj - The URL to return a {@code String}
330
+ * @param {Object|string} obj - The URL to return a {@code String}
331
  * representation of.
331
  * representation of.
332
  * @returns {string} - A {@code String} representation of the specified
332
  * @returns {string} - A {@code String} representation of the specified
333
  * {@code obj} which is supposed to represent a URL.
333
  * {@code obj} which is supposed to represent a URL.
334
  */
334
  */
335
-export function toURLString(obj: ?(string | Object)): ?string {
335
+export function toURLString(obj: ?(Object | string)): ?string {
336
     let str;
336
     let str;
337
 
337
 
338
     switch (typeof obj) {
338
     switch (typeof obj) {

+ 0
- 8
react/features/filmstrip/components/Filmstrip.web.js 查看文件

158
                             onMouseOut = { this._onMouseOut }
158
                             onMouseOut = { this._onMouseOut }
159
                             onMouseOver = { this._onMouseOver } />
159
                             onMouseOver = { this._onMouseOver } />
160
                     </div>
160
                     </div>
161
-                    <audio
162
-                        id = 'userJoined'
163
-                        preload = 'auto'
164
-                        src = 'sounds/joined.wav' />
165
-                    <audio
166
-                        id = 'userLeft'
167
-                        preload = 'auto'
168
-                        src = 'sounds/left.wav' />
169
                 </div>
161
                 </div>
170
             </div>
162
             </div>
171
         );
163
         );

正在加载...
取消
保存