react-native-audio-player-button 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +106 -0
  3. package/lib/commonjs/components/AudioIconComponent.js +45 -0
  4. package/lib/commonjs/components/AudioIconComponent.js.map +1 -0
  5. package/lib/commonjs/components/AudioPlayerButtonComponent.js +166 -0
  6. package/lib/commonjs/components/AudioPlayerButtonComponent.js.map +1 -0
  7. package/lib/commonjs/components/RippleAnimationComponent.js +78 -0
  8. package/lib/commonjs/components/RippleAnimationComponent.js.map +1 -0
  9. package/lib/commonjs/constants/color_constant.js +15 -0
  10. package/lib/commonjs/constants/color_constant.js.map +1 -0
  11. package/lib/commonjs/constants/component_constant.js +20 -0
  12. package/lib/commonjs/constants/component_constant.js.map +1 -0
  13. package/lib/commonjs/constants/icon_constant.js +20 -0
  14. package/lib/commonjs/constants/icon_constant.js.map +1 -0
  15. package/lib/commonjs/helpers/audio_icon_helper.js +28 -0
  16. package/lib/commonjs/helpers/audio_icon_helper.js.map +1 -0
  17. package/lib/commonjs/helpers/ripple_animation_helper.js +44 -0
  18. package/lib/commonjs/helpers/ripple_animation_helper.js.map +1 -0
  19. package/lib/commonjs/index.js +11 -0
  20. package/lib/commonjs/index.js.map +1 -0
  21. package/lib/commonjs/services/audio_player_service.js +81 -0
  22. package/lib/commonjs/services/audio_player_service.js.map +1 -0
  23. package/lib/commonjs/utils/responsive_util.js +14 -0
  24. package/lib/commonjs/utils/responsive_util.js.map +1 -0
  25. package/lib/module/components/AudioIconComponent.js +37 -0
  26. package/lib/module/components/AudioIconComponent.js.map +1 -0
  27. package/lib/module/components/AudioPlayerButtonComponent.js +158 -0
  28. package/lib/module/components/AudioPlayerButtonComponent.js.map +1 -0
  29. package/lib/module/components/RippleAnimationComponent.js +70 -0
  30. package/lib/module/components/RippleAnimationComponent.js.map +1 -0
  31. package/lib/module/constants/color_constant.js +8 -0
  32. package/lib/module/constants/color_constant.js.map +1 -0
  33. package/lib/module/constants/component_constant.js +8 -0
  34. package/lib/module/constants/component_constant.js.map +1 -0
  35. package/lib/module/constants/icon_constant.js +13 -0
  36. package/lib/module/constants/icon_constant.js.map +1 -0
  37. package/lib/module/helpers/audio_icon_helper.js +20 -0
  38. package/lib/module/helpers/audio_icon_helper.js.map +1 -0
  39. package/lib/module/helpers/ripple_animation_helper.js +37 -0
  40. package/lib/module/helpers/ripple_animation_helper.js.map +1 -0
  41. package/lib/module/index.js +3 -0
  42. package/lib/module/index.js.map +1 -0
  43. package/lib/module/services/audio_player_service.js +74 -0
  44. package/lib/module/services/audio_player_service.js.map +1 -0
  45. package/lib/module/utils/responsive_util.js +7 -0
  46. package/lib/module/utils/responsive_util.js.map +1 -0
  47. package/lib/typescript/index.test.d.ts +1 -0
  48. package/lib/typescript/index.test.d.ts.map +1 -0
  49. package/package.json +161 -0
  50. package/src/components/AudioIconComponent.js +40 -0
  51. package/src/components/AudioPlayerButtonComponent.js +170 -0
  52. package/src/components/RippleAnimationComponent.js +61 -0
  53. package/src/constants/color_constant.js +8 -0
  54. package/src/constants/component_constant.js +8 -0
  55. package/src/constants/icon_constant.js +12 -0
  56. package/src/helpers/audio_icon_helper.js +25 -0
  57. package/src/helpers/ripple_animation_helper.js +46 -0
  58. package/src/index.js +2 -0
  59. package/src/services/audio_player_service.js +90 -0
  60. package/src/utils/responsive_util.js +8 -0
package/package.json ADDED
@@ -0,0 +1,161 @@
1
+ {
2
+ "name": "react-native-audio-player-button",
3
+ "version": "1.0.1",
4
+ "description": "test",
5
+ "main": "lib/commonjs/index",
6
+ "module": "lib/module/index",
7
+ "types": "lib/typescript/index.d.ts",
8
+ "react-native": "src/index",
9
+ "source": "src/index",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "android",
14
+ "ios",
15
+ "cpp",
16
+ "*.podspec",
17
+ "!lib/typescript/example",
18
+ "!ios/build",
19
+ "!android/build",
20
+ "!android/gradle",
21
+ "!android/gradlew",
22
+ "!android/gradlew.bat",
23
+ "!android/local.properties",
24
+ "!**/__tests__",
25
+ "!**/__fixtures__",
26
+ "!**/__mocks__",
27
+ "!**/.*"
28
+ ],
29
+ "scripts": {
30
+ "test": "jest",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
33
+ "prepack": "bob build",
34
+ "release": "release-it",
35
+ "example": "yarn --cwd example",
36
+ "bootstrap": "yarn example && yarn install"
37
+ },
38
+ "keywords": [
39
+ "react-native",
40
+ "ios",
41
+ "android"
42
+ ],
43
+ "repository": "https://github.com/limkimsan/react-native-audio-player-button",
44
+ "author": "Kimsanlim <kimsanlim27@gmail.com> (https://github.com/limkimsan)",
45
+ "license": "MIT",
46
+ "bugs": {
47
+ "url": "https://github.com/limkimsan/react-native-audio-player-button/issues"
48
+ },
49
+ "homepage": "https://github.com/limkimsan/react-native-audio-player-button#readme",
50
+ "publishConfig": {
51
+ "registry": "https://registry.npmjs.org/"
52
+ },
53
+ "devDependencies": {
54
+ "@commitlint/config-conventional": "^17.0.2",
55
+ "@evilmartians/lefthook": "^1.2.2",
56
+ "@react-native-community/eslint-config": "^3.0.2",
57
+ "@release-it/conventional-changelog": "^5.0.0",
58
+ "@types/jest": "^28.1.2",
59
+ "@types/react": "~17.0.21",
60
+ "@types/react-native": "0.70.0",
61
+ "commitlint": "^17.0.2",
62
+ "del-cli": "^5.0.0",
63
+ "eslint": "^8.4.1",
64
+ "eslint-config-prettier": "^8.5.0",
65
+ "eslint-plugin-prettier": "^4.0.0",
66
+ "jest": "^28.1.1",
67
+ "pod-install": "^0.1.0",
68
+ "prettier": "^2.0.5",
69
+ "react": "18.1.0",
70
+ "react-native": "0.70.5",
71
+ "react-native-builder-bob": "^0.20.3",
72
+ "release-it": "^15.0.0",
73
+ "typescript": "^4.5.2"
74
+ },
75
+ "resolutions": {
76
+ "@types/react": "17.0.21"
77
+ },
78
+ "peerDependencies": {
79
+ "react": "*",
80
+ "react-native": "*",
81
+ "react-native-sound": "^0.11.2",
82
+ "react-native-vector-icons": ">=9.2.0"
83
+ },
84
+ "engines": {
85
+ "node": ">= 16.0.0"
86
+ },
87
+ "packageManager": "^yarn@1.22.15",
88
+ "jest": {
89
+ "preset": "react-native",
90
+ "modulePathIgnorePatterns": [
91
+ "<rootDir>/example/node_modules",
92
+ "<rootDir>/lib/"
93
+ ]
94
+ },
95
+ "commitlint": {
96
+ "extends": [
97
+ "@commitlint/config-conventional"
98
+ ]
99
+ },
100
+ "release-it": {
101
+ "git": {
102
+ "commitMessage": "chore: release ${version}",
103
+ "tagName": "v${version}"
104
+ },
105
+ "npm": {
106
+ "publish": true
107
+ },
108
+ "github": {
109
+ "release": true
110
+ },
111
+ "plugins": {
112
+ "@release-it/conventional-changelog": {
113
+ "preset": "angular"
114
+ }
115
+ }
116
+ },
117
+ "eslintConfig": {
118
+ "root": true,
119
+ "extends": [
120
+ "@react-native-community",
121
+ "prettier"
122
+ ],
123
+ "rules": {
124
+ "prettier/prettier": [
125
+ "error",
126
+ {
127
+ "quoteProps": "consistent",
128
+ "singleQuote": true,
129
+ "tabWidth": 2,
130
+ "trailingComma": "es5",
131
+ "useTabs": false
132
+ }
133
+ ]
134
+ }
135
+ },
136
+ "eslintIgnore": [
137
+ "node_modules/",
138
+ "lib/"
139
+ ],
140
+ "prettier": {
141
+ "quoteProps": "consistent",
142
+ "singleQuote": true,
143
+ "tabWidth": 2,
144
+ "trailingComma": "es5",
145
+ "useTabs": false
146
+ },
147
+ "react-native-builder-bob": {
148
+ "source": "src",
149
+ "output": "lib",
150
+ "targets": [
151
+ "commonjs",
152
+ "module",
153
+ [
154
+ "typescript",
155
+ {
156
+ "project": "tsconfig.build.json"
157
+ }
158
+ ]
159
+ ]
160
+ }
161
+ }
@@ -0,0 +1,40 @@
1
+ import React from 'react'
2
+
3
+ import color from '../constants/color_constant';
4
+ import {defaultIconSize, primaryIconColor, secondaryIconColor} from '../constants/component_constant';
5
+ import audioIconHelper from '../helpers/audio_icon_helper';
6
+
7
+ const AudioIconComponent = (props) => {
8
+ const getIconColor = () => {
9
+ const primaryColor = props.iconPrimaryColor || primaryIconColor
10
+ const secondaryColor = props.iconSecondaryColor || secondaryIconColor
11
+ return props.isPlaying ? secondaryColor : primaryColor;
12
+ }
13
+
14
+ const iconSize = props.iconSize || defaultIconSize
15
+
16
+ {/* CloneElement is used so we can pass different type of icon and still using the same configuration */}
17
+ return (
18
+ React.cloneElement(audioIconHelper.getIcon(props.customIcon, props.isSpeakerIcon), {
19
+ name: audioIconHelper.getIconName(props.customIconSet, props.isSpeakerIcon, props.audio, props.isPlaying),
20
+ size: iconSize, color: !!props.audio ? getIconColor() : color.muted,
21
+ style: [props.iconStyle, { width: iconSize, marginLeft: (props.isPlaying && !props.isSpeakerIcon) ? -2 : 0 }]
22
+ })
23
+ )
24
+ }
25
+
26
+ export default AudioIconComponent;
27
+
28
+ {/*
29
+ <AudioIconComponent
30
+ isPlaying={boolean}
31
+ audio={audio's object}
32
+ isSpeakerIcon={boolean}
33
+ iconStyle={{}}
34
+ iconSize={number}
35
+ primaryColor={}
36
+ secondaryColor={}
37
+ customIcon={Icon component}
38
+ customIconSet={{play: '', pause: '', mute: ''}}
39
+ />
40
+ */}
@@ -0,0 +1,170 @@
1
+ import React, { useEffect, useState, useRef } from 'react'
2
+ import {TouchableOpacity, View} from 'react-native'
3
+
4
+ import AudioIconComponent from './AudioIconComponent';
5
+ import RippleAnimationComponent from './RippleAnimationComponent';
6
+ import {defaultBtnSize} from '../constants/component_constant'
7
+ import color from '../constants/color_constant';
8
+ import audioPlayerService from '../services/audio_player_service';
9
+
10
+ const AudioPlayerButtonComponent = (props) => {
11
+ const localAudioPlayer = useRef(null);
12
+ const [state, setState] = useState({
13
+ playSeconds: 0,
14
+ duration: 0,
15
+ countInterval: null,
16
+ });
17
+ const [isPlaying, setIsPlaying] = useState(false);
18
+
19
+ useEffect(() => {
20
+ // Clear the local audio if the user is switching to play another audio
21
+ if (!!props.playingUuid && props.playingUuid != props.itemUuid)
22
+ clearLocalAudioPlayer();
23
+
24
+ // Clear all the audio if the playingUuid is null (ex: exit the screen)
25
+ if (!props.playingUuid && !!global.audioPlayer && !!localAudioPlayer.current) {
26
+ clearLocalAudioPlayer();
27
+ audioPlayerService.clearAllAudio();
28
+ }
29
+ }, [props.playingUuid])
30
+
31
+ useEffect(() => {
32
+ return () => { audioPlayerService.clearAllAudio(); } // Clear all the audio when component is unmount
33
+ }, []);
34
+
35
+ const clearLocalAudioPlayer = () => {
36
+ localAudioPlayer.current = null;
37
+ setState({
38
+ playSeconds: 0,
39
+ duration: 0,
40
+ countInterval: null
41
+ })
42
+ setIsPlaying(false)
43
+ }
44
+
45
+ const updateState = (playSeconds, duration, countInterval) => {
46
+ setState({ playSeconds, duration, countInterval })
47
+ }
48
+
49
+ const handleLocalAudioPlayer = () => {
50
+ if (!props.allowPause) {
51
+ audioPlayerService.clearAllAudio();
52
+ clearLocalAudioPlayer();
53
+ props.updatePlayingUuid(null);
54
+ return;
55
+ }
56
+ audioPlayerService.playPause(localAudioPlayer.current, state.countInterval, (audioPlayer, playSeconds, duration, countInterval) => {
57
+ handleAudioCallback(audioPlayer, playSeconds, duration, countInterval)
58
+ });
59
+ }
60
+
61
+ const toggleAudio = () => {
62
+ if (!!localAudioPlayer.current)
63
+ return handleLocalAudioPlayer();
64
+
65
+ audioPlayerService.clearAllAudio(); // Clear all the playing audio when starting to play a new audio if there is an existing audio is playing
66
+ audioPlayerService.play(props.audio, props.itemUuid, props.isFromAppBundle || false, props.playingUuid, (audioPlayer, playSeconds, duration, countInterval) => {
67
+ handleAudioCallback(audioPlayer, playSeconds, duration, countInterval)
68
+ });
69
+ }
70
+
71
+ const handleAudioCallback = (audioPlayer, playSeconds, duration, countInterval) => {
72
+ global.audioPlayer = audioPlayer;
73
+ global.countInterval = countInterval;
74
+ localAudioPlayer.current = audioPlayer;
75
+ updateState(playSeconds, duration, countInterval);
76
+ handleStopPlaying(countInterval, playSeconds, duration);
77
+ }
78
+
79
+ const handleStopPlaying = (countInterval, playSeconds, duration) => {
80
+ if (!countInterval) {
81
+ setIsPlaying(false);
82
+
83
+ if (playSeconds == 0 && duration == 0)
84
+ props.updatePlayingUuid(null);
85
+ }
86
+ }
87
+
88
+ const onPress = () => {
89
+ props.updatePlayingUuid(props.itemUuid);
90
+ setIsPlaying(!isPlaying);
91
+ toggleAudio();
92
+ }
93
+
94
+ const renderRippleAnimation = () => {
95
+ return <RippleAnimationComponent
96
+ rippleColor={props.rippleColor}
97
+ height={props.rippleHeight}
98
+ width={props.rippleWidth}
99
+ radius={props.rippleRadius}
100
+ isPlaying={isPlaying}
101
+ rippleStyle={props.rippleStyle}
102
+ />
103
+ }
104
+
105
+ const renderBtn = () => {
106
+ const btnStyles = {
107
+ alignItems: 'center',
108
+ backgroundColor: props.buttonColor || color.white,
109
+ borderRadius: 48,
110
+ elevation: props.hasShadow ? 4 : 0,
111
+ justifyContent: 'center',
112
+ height: props.buttonHeight || defaultBtnSize,
113
+ width: props.buttonWidth || defaultBtnSize,
114
+ zIndex: 10
115
+ }
116
+
117
+ return <TouchableOpacity onPress={() => onPress()} disabled={!props.audio} style={[btnStyles, props.buttonStyle]}>
118
+ <AudioIconComponent
119
+ isPlaying={isPlaying}
120
+ audio={props.audio}
121
+ isSpeakerIcon={props.isSpeakerIcon}
122
+ iconStyle={props.iconStyle}
123
+ iconSize={props.iconSize}
124
+ iconPrimaryColor={props.iconPrimaryColor}
125
+ iconSecondaryColor={props.iconSecondaryColor}
126
+ customIcon={props.customIcon}
127
+ customIconSet={props.customIconSet}
128
+ />
129
+ </TouchableOpacity>
130
+ }
131
+
132
+ return (
133
+ <View style={[{justifyContent: 'center', alignItems: 'center'}, props.containerStyle]}>
134
+ { props.rippled && renderRippleAnimation() }
135
+ { renderBtn() }
136
+ </View>
137
+ )
138
+ }
139
+
140
+ export default AudioPlayerButtonComponent
141
+
142
+ // How to use
143
+ {/*
144
+ <AudioPlayerServcie
145
+ audio={}
146
+ itemUuid={string}
147
+ isFromAppBundle={boolean}
148
+ playingUuid={string}
149
+ isSpeakerIcon={boolean}
150
+ buttonColor={}
151
+ buttonHeight={number}
152
+ buttonWidth={number}
153
+ allowPause={boolean}
154
+ rippled={boolean}
155
+ rippleColor={}
156
+ rippleHeight={number}
157
+ rippleWidth={number}
158
+ rippleRadius={number}
159
+ rippleStyle={{}}
160
+ iconStyle={{}}
161
+ iconSize={number}
162
+ iconPrimaryColor={}
163
+ iconSecondaryColor={}
164
+ buttonStyle={{}}
165
+ updatePlayingUuid={(playingUuid) => {}}
166
+ containerStyle={{}}
167
+ customIcon={icon component}
168
+ customIconSet={{play: '', pause: '', mute: ''}}
169
+ />
170
+ */}
@@ -0,0 +1,61 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { Animated, StyleSheet } from 'react-native';
3
+ import color from '../constants/color_constant';
4
+ import {defaultRippleSize} from '../constants/component_constant';
5
+ import rippleAnimationHelper from '../helpers/ripple_animation_helper';
6
+
7
+ const RippleAnimationComponent = (props) => {
8
+ const [componentDidMount, setComponentDidMount] = useState(false);
9
+ const [state] = useState({
10
+ opacity1: new Animated.Value(1),
11
+ opacity2: new Animated.Value(1),
12
+ opacity3: new Animated.Value(1),
13
+ scale1: new Animated.Value(0),
14
+ scale2: new Animated.Value(0),
15
+ scale3: new Animated.Value(0),
16
+ });
17
+
18
+ const animations = [
19
+ { scale: state.scale1, opacity: state.opacity1 },
20
+ { scale: state.scale2, opacity: state.opacity2 },
21
+ { scale: state.scale3, opacity: state.opacity3 },
22
+ ];
23
+
24
+ useEffect(() => {
25
+ if (!componentDidMount) {
26
+ setComponentDidMount(true);
27
+ return;
28
+ }
29
+
30
+ props.isPlaying ? rippleAnimationHelper.start(animations) : rippleAnimationHelper.reset(animations);
31
+ }, [props.isPlaying])
32
+
33
+ const rippleView = (index) => {
34
+ const rippleStyle = {
35
+ backgroundColor: props.rippleColor || color.black,
36
+ radius: props.radius || defaultRippleSize,
37
+ height: props.height || defaultRippleSize,
38
+ width: props.width || defaultRippleSize
39
+ }
40
+
41
+ return <Animated.View key={index} style={[ StyleSheet.absoluteFillObject, { backgroundColor: rippleStyle.backgroundColor }, props.rippleStyle,
42
+ {opacity: animations[index].opacity, transform: [{ scale: animations[index].scale }], height: rippleStyle.height, width: rippleStyle.width, borderRadius: rippleStyle.radius }
43
+ ]} />
44
+ }
45
+
46
+ return [...Array(3).keys()].map((index) => rippleView(index))
47
+ }
48
+
49
+ export default RippleAnimationComponent;
50
+
51
+ // How to use
52
+ {/*
53
+ <Ripple
54
+ color={}
55
+ height={number}
56
+ width={number}
57
+ radius={number}
58
+ isPlaying={boolean}
59
+ rippleStyle={{}}
60
+ />
61
+ */}
@@ -0,0 +1,8 @@
1
+ const color = {
2
+ black: '#000000',
3
+ gray: '#808080',
4
+ muted: '#6c757d',
5
+ white: '#ffffff',
6
+ }
7
+
8
+ export default color
@@ -0,0 +1,8 @@
1
+ import {isLowPixelDensityDevice} from '../utils/responsive_util'
2
+ import color from './color_constant';
3
+
4
+ export const defaultBtnSize = isLowPixelDensityDevice() ? 48 : 56
5
+ export const defaultRippleSize = isLowPixelDensityDevice() ? 48 : 56
6
+ export const defaultIconSize = isLowPixelDensityDevice() ? 24 : 26
7
+ export const primaryIconColor = color.black
8
+ export const secondaryIconColor = color.gray
@@ -0,0 +1,12 @@
1
+ export const defaultAudioIconSet = {
2
+ 'ion_icon': {
3
+ play: 'volume-high-outline',
4
+ pause: 'pause',
5
+ mute: 'volume-mute-outline'
6
+ },
7
+ 'feather': {
8
+ play: 'play',
9
+ pause: 'pause',
10
+ mute: 'play'
11
+ }
12
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import FeatherIcon from 'react-native-vector-icons/Feather';
3
+ import IonIcon from 'react-native-vector-icons/Ionicons';
4
+ import {defaultAudioIconSet} from '../constants/icon_constant';
5
+
6
+ const audioIconHelper = (() => {
7
+ return {
8
+ getIcon,
9
+ getIconName,
10
+ }
11
+
12
+ function getIcon(customIcon, isSpeakerIcon) {
13
+ return customIcon ? customIcon : isSpeakerIcon ? <IonIcon/> : <FeatherIcon/>
14
+ }
15
+
16
+ function getIconName(customIconSet, isSpeakerIcon, audio, isPlaying) {
17
+ const icon = customIconSet ? customIconSet : isSpeakerIcon ? defaultAudioIconSet['ion_icon'] : defaultAudioIconSet['feather'];
18
+ if (!audio)
19
+ return icon.mute;
20
+
21
+ return isPlaying ? icon.pause : icon.play;
22
+ }
23
+ })()
24
+
25
+ export default audioIconHelper
@@ -0,0 +1,46 @@
1
+ import { Animated } from 'react-native';
2
+
3
+ const rippleAnimationHelper = (() => {
4
+ return {
5
+ start,
6
+ reset,
7
+ }
8
+
9
+ function start(animations) {
10
+ animations.map((animation, index) => {
11
+ Animated.loop(
12
+ Animated.parallel([
13
+ _animatedTiming(animation.scale, 1.8, index * 400),
14
+ _animatedTiming(animation.opacity, 0, index * 400),
15
+ ], { useNativeDriver: true })
16
+ ).start();
17
+ });
18
+ }
19
+
20
+ function reset(animations) {
21
+ Animated.loop(
22
+ Animated.parallel(_allAnimatedTimings(animations))
23
+ ).reset();
24
+ }
25
+
26
+ // private methods
27
+ function _animatedTiming(type, toValue, delay) {
28
+ return Animated.timing(type, {
29
+ toValue: toValue,
30
+ duration: 2000,
31
+ delay: delay,
32
+ useNativeDriver: true
33
+ })
34
+ }
35
+
36
+ function _allAnimatedTimings(animations) {
37
+ const animated = [];
38
+ animations.map(animation => {
39
+ animated.push(Animated.timing(animation.scale));
40
+ animated.push(Animated.timing(animation.opacity));
41
+ });
42
+ return animated;
43
+ }
44
+ })();
45
+
46
+ export default rippleAnimationHelper;
package/src/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import AudioPlayerButton from './components/AudioPlayerButtonComponent';
2
+ export default AudioPlayerButton;
@@ -0,0 +1,90 @@
1
+ import Sound from 'react-native-sound';
2
+
3
+ // Notice: The audio file must have the Bit Rate Mode as Constant in order to prevent the library from
4
+ // returning invalid audio duration
5
+
6
+ const audioPlayerService = (() => {
7
+ return {
8
+ play,
9
+ playPause,
10
+ stop,
11
+ clearAllAudio,
12
+ }
13
+
14
+ function play(filename, itemUuId, isFromAppBundle, playingUuId, callback) {
15
+ if (itemUuId == playingUuId) return; // prevent the player from playing the same audio muliple time overlap each other
16
+
17
+ const audioPlayer = isFromAppBundle ? new Sound(filename, Sound.MAIN_BUNDLE, (error) => _handlePlayCallback(error, audioPlayer, callback))
18
+ : new Sound(filename, (error) => _handlePlayCallback(error, audioPlayer, callback))
19
+ }
20
+
21
+ function playPause(audioPlayer, countInterval, callback) {
22
+ if (!!countInterval) {
23
+ clearInterval(countInterval);
24
+ audioPlayer.pause();
25
+ audioPlayer.getCurrentTime((seconds) => {
26
+ callback(audioPlayer, seconds, audioPlayer.getDuration(), null);
27
+ });
28
+ return;
29
+ }
30
+
31
+ _playAudio(audioPlayer, callback, countInterval);
32
+ }
33
+
34
+ function stop(audioPlayer, countInterval) {
35
+ clearInterval(countInterval);
36
+ if (!audioPlayer)
37
+ return;
38
+
39
+ audioPlayer.stop();
40
+ }
41
+
42
+ function clearAllAudio() {
43
+ if (!!global.audioPlayer || !!global.countInterval) {
44
+ global.audioPlayer.release();
45
+ clearInterval(global.countInterval)
46
+ global.audioPlayer = null;
47
+ global.countInterval = null;
48
+ }
49
+ }
50
+
51
+ // private method
52
+ function _countPlaySeconds(audioPlayer, callback) {
53
+ if (!audioPlayer)
54
+ return null;
55
+
56
+ const countInterval = setInterval(() => {
57
+ audioPlayer.getCurrentTime((seconds) => {
58
+ if (seconds == audioPlayer.getDuration())
59
+ return clearInterval(countInterval);
60
+
61
+ callback(audioPlayer, seconds, audioPlayer.getDuration(), countInterval);
62
+ });
63
+ }, 150); // Use 150 ms to make the slider move smoother
64
+
65
+ return countInterval;
66
+ }
67
+
68
+ function _playAudio(audioPlayer, callback, countInterval = null) {
69
+ // Clear the previous counting and start a new count when start playing the audio
70
+ clearInterval(countInterval);
71
+ const countPlaySeconds = _countPlaySeconds(audioPlayer, callback);
72
+ audioPlayer.play((success) => {
73
+ if (success) {
74
+ clearInterval(countPlaySeconds);
75
+ audioPlayer.release();
76
+ callback(null, 0, 0, null); // reset the audioPlayer, playSeconds, duration and countInterval
77
+ }
78
+ else
79
+ console.log('playback failed due to audio decoding errors');
80
+ });
81
+ }
82
+
83
+ function _handlePlayCallback(error, audioPlayer, callback) {
84
+ if (!!error) return console.log('failed to play audio = ', error);
85
+
86
+ _playAudio(audioPlayer, callback);
87
+ }
88
+ })();
89
+
90
+ export default audioPlayerService;
@@ -0,0 +1,8 @@
1
+ import {PixelRatio } from 'react-native';
2
+
3
+ const XHDPIRatio = 2;
4
+
5
+ export const isLowPixelDensityDevice = () => {
6
+ const devicePixelRatio = Math.round(PixelRatio.roundToNearestPixel(PixelRatio.get()));
7
+ return devicePixelRatio <= XHDPIRatio
8
+ }