Skip to content

Commit

Permalink
feat(UNT-T27088): external url support
Browse files Browse the repository at this point in the history
  • Loading branch information
nilesh-simform authored and prince-d-simform committed Nov 14, 2024
1 parent 1e538d0 commit 49bf4dd
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 74 deletions.
40 changes: 36 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ Here's how to get started with react-native-audio-waveform in your React Native
##### 1. Install the package

```sh
npm install @simform_solutions/react-native-audio-waveform react-native-gesture-handler
npm install @simform_solutions/react-native-audio-waveform rn-fetch-blob react-native-gesture-handler
```

###### --- or ---

```sh
yarn add @simform_solutions/react-native-audio-waveform react-native-gesture-handler
yarn add @simform_solutions/react-native-audio-waveform rn-fetch-blob react-native-gesture-handler
```

##### 2. Install CocoaPods in the iOS project
Expand All @@ -48,7 +48,7 @@ yarn add @simform_solutions/react-native-audio-waveform react-native-gesture-han
npx pod-install
```

##### Know more about [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler)
##### Know more about [rn-fetch-blob](https://www.npmjs.com/package/rn-fetch-blob) and [react-native-gesture-handler](https://www.npmjs.com/package/react-native-gesture-handler)

##### 3. Add audio recording permissions

Expand Down Expand Up @@ -90,7 +90,34 @@ const ref = useRef<IWaveformRef>(null);
<Waveform
mode="static"
ref={ref}
path={item}
path={path}
candleSpace={2}
candleWidth={4}
scrubColor="white"
onPlayerStateChange={playerState => console.log(playerState)}
onPanStateChange={isMoving => console.log(isMoving)}
/>;
```

When you want to show a waveform for a external audio URL, you need to use `static` mode for the waveform and set isExternalUrl to true.

Check the example below for more information.

```tsx
import {
Waveform,
type IWaveformRef,
} from '@simform_solutions/react-native-audio-waveform';

const url = 'https://www2.cs.uic.edu/~i101/SoundFiles/taunt.wav'; // URL to the audio file for which you want to show waveform
const ref = useRef<IWaveformRef>(null);
<Waveform
mode="static"
ref={ref}
path={url}
isExternalUrl={true}
onDownloadStateChange={state => console.log(state)}
onDownloadProgressChange={progress => console.log(progress)}
candleSpace={2}
candleWidth={4}
scrubColor="white"
Expand Down Expand Up @@ -133,6 +160,9 @@ You can check out the full example at [Example](./example/src/App.tsx).
| ref\* | - ||| IWaveformRef | Type of ref provided to waveform component. If waveform mode is `static`, some methods from ref will throw error and same for `live`.<br> Check [IWaveformRef](#iwaveformref-methods) for more details about which methods these refs provides. |
| path\* | - ||| string | Used for `static` type. It is the resource path of an audio source file. |
| playbackSpeed | 1.0 ||| 1.0 / 1.5 / 2.0 | The playback speed of the audio player. Note: Currently playback speed only supports, Normal (1x) Faster(1.5x) and Fastest(2.0x), any value passed to playback speed greater than 2.0 will be automatically adjusted to normal playback speed |
| volume | 3 ||| number | Used for `static` type. It is a volume level for the media player, ranging from 1 to 10. |
| isExternalUrl | false ||| boolean | Used for `static` type. If the resource path of an audio file is a URL, then pass true; otherwise, pass false. |
| downloadExternalAudio | true ||| boolean | Used for `static` type. Indicates whether the external media should be downloaded. |
| candleSpace | 2 ||| number | Space between two candlesticks of waveform |
| candleWidth | 5 ||| number | Width of single candlestick of waveform |
| candleHeightScale | 3 ||| number | Scaling height of candlestick of waveform |
Expand All @@ -145,6 +175,8 @@ You can check out the full example at [Example](./example/src/App.tsx).
| onRecorderStateChange | - ||| ( recorderState : RecorderState ) => void | callback function which returns the recorder state whenever the recorder state changes. Check RecorderState for more details |
| onCurrentProgressChange | - ||| ( currentProgress : number, songDuration: number ) => void | callback function, which returns current progress of audio and total song duration. |
| onChangeWaveformLoadState | - ||| ( state : boolean ) => void | callback function which returns the loading state of waveform candlestick. |
| onDownloadStateChange | - ||| ( state : boolean ) => void | A callback function that returns the loading state of a file download from an external URL. |
| onDownloadProgressChange | - ||| ( currentProgress : number ) => void | Used when isExternalUrl is true; a callback function that returns the current progress of a file download from an external URL |
| onError | - ||| ( error : Error ) => void | callback function which returns the error for static audio waveform |

##### Know more about [ViewStyle](https://reactnative.dev/docs/view-style-props), [PlayerState](#playerstate), and [RecorderState](#recorderstate)
Expand Down
51 changes: 47 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
ScrollView,
StatusBar,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
Expand Down Expand Up @@ -52,27 +53,40 @@ const RenderListItem = React.memo(
onPanStateChange,
currentPlaybackSpeed,
changeSpeed,
isExternalUrl = false,
}: {
item: ListItem;
currentPlaying: string;
setCurrentPlaying: Dispatch<SetStateAction<string>>;
onPanStateChange: (value: boolean) => void;
currentPlaybackSpeed: PlaybackSpeedType;
changeSpeed: () => void;
isExternalUrl?: boolean;
}) => {
const ref = useRef<IWaveformRef>(null);
const [playerState, setPlayerState] = useState(PlayerState.stopped);
const styles = stylesheet({ currentUser: item.fromCurrentUser });
const [isLoading, setIsLoading] = useState(true);
const [isLoading, setIsLoading] = useState(isExternalUrl ? false : true);
const [downloadExternalAudio, setDownloadExternalAudio] = useState(false);
const [isAudioDownloaded, setIsAudioDownloaded] = useState(false);

const handleButtonAction = () => {
const handleButtonAction = (): void => {
if (playerState === PlayerState.stopped) {
setCurrentPlaying(item.path);
} else {
setCurrentPlaying('');
}
};

const handleDownloadPress = (): void => {
setDownloadExternalAudio(true);
if (currentPlaying === item.path) {
setCurrentPlaying('');
}

setIsLoading(true);
};

useEffect(() => {
if (currentPlaying !== item.path) {
ref.current?.stopPlayer();
Expand All @@ -82,15 +96,23 @@ const RenderListItem = React.memo(
}, [currentPlaying]);

return (
<View key={item.path} style={[styles.listItemContainer]}>
<View
key={item.path}
style={[
styles.listItemContainer,
item.fromCurrentUser &&
isExternalUrl &&
!isAudioDownloaded &&
styles.listItemReverseContainer,
]}>
<View style={styles.listItemWidth}>
<View style={[styles.buttonContainer]}>
<Pressable
disabled={isLoading}
onPress={handleButtonAction}
style={styles.playBackControlPressable}>
{isLoading ? (
<ActivityIndicator color={'#FF0000'} />
<ActivityIndicator color={'#FFFFFF'} />
) : (
<FastImage
source={
Expand All @@ -115,6 +137,7 @@ const RenderListItem = React.memo(
scrubColor={Colors.white}
waveColor={Colors.lightWhite}
candleHeightScale={4}
downloadExternalAudio={downloadExternalAudio}
onPlayerStateChange={state => {
setPlayerState(state);
if (
Expand All @@ -124,10 +147,20 @@ const RenderListItem = React.memo(
setCurrentPlaying('');
}
}}
isExternalUrl={isExternalUrl}
onPanStateChange={onPanStateChange}
onError={error => {
console.log(error, 'we are in example');
}}
onDownloadStateChange={state => {
console.log('Download State', state);
}}
onDownloadProgressChange={progress => {
console.log('Download Progress', `${progress}%`);
if (progress === 100) {
setIsAudioDownloaded(true);
}
}}
onCurrentProgressChange={(currentProgress, songDuration) => {
console.log(
'currentProgress ',
Expand All @@ -151,6 +184,15 @@ const RenderListItem = React.memo(
)}
</View>
</View>
{isExternalUrl && !downloadExternalAudio && !isAudioDownloaded ? (
<TouchableOpacity onPress={handleDownloadPress}>
<Image
source={Icons.download}
style={styles.downloadIcon}
resizeMode="contain"
/>
</TouchableOpacity>
) : null}
</View>
);
}
Expand Down Expand Up @@ -328,6 +370,7 @@ const AppContainer = () => {
currentPlaying={currentPlaying}
setCurrentPlaying={setCurrentPlaying}
item={item}
isExternalUrl={item.isExternalUrl}
onPanStateChange={value => setShouldScroll(!value)}
{...{ currentPlaybackSpeed, changeSpeed }}
/>
Expand Down
Binary file added example/src/assets/icons/download.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions example/src/assets/icons/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const Icons = {
mic: require('./mic.png'),
logo: require('./logo.png'),
delete: require('./delete.png'),
download: require('./download.png'),
};
45 changes: 36 additions & 9 deletions example/src/constants/Audios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Platform } from 'react-native';
export interface ListItem {
fromCurrentUser: boolean;
path: string;
isExternalUrl?: boolean;
}

/**
Expand Down Expand Up @@ -70,30 +71,56 @@ const audioAssetArray = [
'file_example_mp3_15s.mp3',
];

const externalAudioAssetArray = [
'https://codeskulptor-demos.commondatastorage.googleapis.com/GalaxyInvaders/theme_01.mp3',
'https://codeskulptor-demos.commondatastorage.googleapis.com/pang/paza-moduless.mp3',
];

/**
* Retrieve previously recorded audio files from the cache/document directory.
* @returns
* @returns
*/
export const getRecordedAudios = async (): Promise<string[]> => {
const recordingSavingPath = Platform.select({ ios: fs.DocumentDirectoryPath, default: fs.CachesDirectoryPath })
const recordingSavingPath = Platform.select({
ios: fs.DocumentDirectoryPath,
default: fs.CachesDirectoryPath,
});

const items = await fs.readDir(recordingSavingPath)
return items.filter(item => item.path.endsWith('.m4a')).map(item => item.path)
}
const items = await fs.readDir(recordingSavingPath);
return items
.filter(item => item.path.endsWith('.m4a'))
.map(item => item.path);
};

/**
* Generate a list of file objects with information about successfully copied files (Android)
* or all files (iOS).
* @returns {Promise<ListItem[]>} A Promise that resolves to the list of file objects.
*/
export const generateAudioList = async (): Promise<ListItem[]> => {
const audioAssetPaths = (await copyFilesToNativeResources()).map(value => `${filePath}/${value}`);
const recordedAudios = await getRecordedAudios()
const audioAssetPaths = (await copyFilesToNativeResources()).map(
value => `${filePath}/${value}`
);
const recordedAudios = await getRecordedAudios();

// Generate the final list based on the copied or available files
return [...audioAssetPaths, ...recordedAudios].map?.((value, index) => ({
fromCurrentUser: index % 2 !== 0,
const localAssetList = [...audioAssetPaths, ...recordedAudios].map?.(
value => ({
path: value,
})
);

const externalAudioList = externalAudioAssetArray.map(value => ({
path: value,
isExternalUrl: true,
}));

const finalAudios = [...localAssetList, ...externalAudioList].map(
(value, index) => ({
...value,
fromCurrentUser: index % 2 !== 0,
})
);

return finalAudios;
};
17 changes: 15 additions & 2 deletions example/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,16 @@ const styles = (params: StyleSheetParams = {}) =>
},
listItemContainer: {
marginTop: scale(16),
alignItems: params.currentUser ? 'flex-end' : 'flex-start',
flexDirection: 'row',
justifyContent: params.currentUser ? 'flex-end' : 'flex-start',
alignItems: 'center',
},
listItemReverseContainer: {
flexDirection: 'row-reverse',
alignSelf: 'flex-end',
},
listItemWidth: {
width: '90%',
width: '88%',
},
buttonImage: {
height: scale(22),
Expand Down Expand Up @@ -129,6 +135,13 @@ const styles = (params: StyleSheetParams = {}) =>
textAlign: 'center',
fontWeight: '600',
},
downloadIcon: {
width: 20,
height: 20,
tintColor: Colors.pink,
marginLeft: 10,
marginRight: 10,
},
});

export default styles;
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
]
},
"dependencies": {
"lodash": "^4.17.21"
"lodash": "^4.17.21",
"rn-fetch-blob": "^0.12.0"
}
}
}
Loading

0 comments on commit 49bf4dd

Please sign in to comment.