diff --git a/README.md b/README.md
index 8f75eed..46559db 100644
--- a/README.md
+++ b/README.md
@@ -127,6 +127,7 @@ You can check out the full example at [Example](./example/src/App.tsx).
| mode\* | - | ✅ | ✅ | 'live' or 'static' | Type of waveform. It can be either `static` for the resource file or `live` if you want to record audio |
| 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`.
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. |
+| isExternalUrl | false | ✅ | ❌ | boolean | Used for `static` type. If the resource path of an audio file is a URL, then pass true; otherwise, pass false. |
| 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 |
@@ -138,6 +139,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)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 29ec694..4b123d0 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -41,11 +41,13 @@ const ListItem = React.memo(
currentPlaying,
setCurrentPlaying,
onPanStateChange,
+ isExternalUrl = false,
}: {
item: ListItem;
currentPlaying: string;
setCurrentPlaying: Dispatch>;
onPanStateChange: (value: boolean) => void;
+ isExternalUrl?: boolean;
}) => {
const ref = useRef(null);
const [playerState, setPlayerState] = useState(PlayerState.stopped);
@@ -116,10 +118,17 @@ const ListItem = 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}%`);
+ }}
onCurrentProgressChange={(currentProgress, songDuration) => {
console.log(
'currentProgress ',
@@ -241,6 +250,7 @@ const AppContainer = () => {
currentPlaying={currentPlaying}
setCurrentPlaying={setCurrentPlaying}
item={item}
+ isExternalUrl={item.isExternalUrl}
onPanStateChange={value => setShouldScroll(!value)}
/>
))}
diff --git a/example/src/constants/Audios.ts b/example/src/constants/Audios.ts
index ef9b878..5f8915c 100644
--- a/example/src/constants/Audios.ts
+++ b/example/src/constants/Audios.ts
@@ -5,6 +5,7 @@ import { globalMetrics } from '../../src/theme';
export interface ListItem {
fromCurrentUser: boolean;
path: string;
+ isExternalUrl?: boolean;
}
/**
@@ -54,15 +55,28 @@ 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',
+];
+
copyFilesToAndroidResources();
/**
* List of file objects with information about the files.
* @type {ListItem[]}
*/
-export const audioListArray: ListItem[] = audioAssetArray.map(
+const audioList: ListItem[] = audioAssetArray.map((value, index) => ({
+ fromCurrentUser: index % 2 !== 0,
+ path: `${filePath}/${value}`,
+}));
+
+const externalAudioList: ListItem[] = externalAudioAssetArray.map(
(value, index) => ({
fromCurrentUser: index % 2 !== 0,
- path: `${filePath}/${value}`,
+ path: value,
+ isExternalUrl: true,
})
);
+
+export const audioListArray: ListItem[] = [...audioList, ...externalAudioList];
diff --git a/package.json b/package.json
index 2736127..24d7ecc 100644
--- a/package.json
+++ b/package.json
@@ -110,6 +110,7 @@
]
},
"dependencies": {
- "lodash": "^4.17.21"
+ "lodash": "^4.17.21",
+ "rn-fetch-blob": "^0.12.0"
}
-}
\ No newline at end of file
+}
diff --git a/src/components/Waveform/Waveform.tsx b/src/components/Waveform/Waveform.tsx
index 6a90cd0..13dadcb 100644
--- a/src/components/Waveform/Waveform.tsx
+++ b/src/components/Waveform/Waveform.tsx
@@ -36,6 +36,7 @@ import {
type LiveWaveform,
type StaticWaveform,
} from './WaveformTypes';
+import RNFetchBlob from 'rn-fetch-blob';
export const Waveform = forwardRef((props, ref) => {
const {
@@ -53,8 +54,14 @@ export const Waveform = forwardRef((props, ref) => {
onCurrentProgressChange = () => {},
candleHeightScale = 3,
onChangeWaveformLoadState,
+ isExternalUrl = false,
+ onDownloadStateChange,
+ onDownloadProgressChange,
} = props as StaticWaveform & LiveWaveform;
const viewRef = useRef(null);
+ const [audioPath, setAudioPath] = useState(
+ !isExternalUrl ? path : undefined
+ );
const scrollRef = useRef(null);
const [waveform, setWaveform] = useState([]);
const [viewLayout, setViewLayout] = useState(null);
@@ -86,12 +93,42 @@ export const Waveform = forwardRef((props, ref) => {
const { checkHasAudioRecorderPermission } = useAudioPermission();
+ useEffect(() => {
+ if (isExternalUrl && path) {
+ (onDownloadStateChange as Function)?.(true);
+
+ RNFetchBlob.config({
+ fileCache: true,
+ })
+ .fetch('GET', path)
+ .progress((received, total) => {
+ let progressPercentage: number = Number(
+ ((received / total) * 100).toFixed(2)
+ );
+ (onDownloadProgressChange as Function)?.(progressPercentage);
+ })
+ .then(res => {
+ // the temp file path
+ setAudioPath(res.path());
+ (onDownloadStateChange as Function)?.(false);
+ (onDownloadProgressChange as Function)?.(100);
+ })
+ .catch(e => {
+ console.log(e);
+ (onDownloadStateChange as Function)?.(false);
+ });
+ } else {
+ (onDownloadStateChange as Function)?.(false);
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isExternalUrl, path]);
+
const preparePlayerForPath = async () => {
- if (!isNil(path) && !isEmpty(path)) {
+ if (!isNil(audioPath) && !isEmpty(audioPath)) {
try {
const prepare = await preparePlayer({
- path,
- playerKey: `PlayerFor${path}`,
+ path: audioPath,
+ playerKey: `PlayerFor${audioPath}`,
updateFrequency: UpdateFrequency.medium,
volume: 10,
});
@@ -101,7 +138,7 @@ export const Waveform = forwardRef((props, ref) => {
}
} else {
return Promise.reject(
- new Error(`Can not start player for path: ${path}`)
+ new Error(`Can not start player for path: ${audioPath}`)
);
}
};
@@ -109,14 +146,14 @@ export const Waveform = forwardRef((props, ref) => {
const getAudioDuration = async () => {
try {
const duration = await getDuration({
- playerKey: `PlayerFor${path}`,
+ playerKey: `PlayerFor${audioPath}`,
durationType: DurationType.max,
});
if (!isNil(duration)) {
setSongDuration(duration);
} else {
return Promise.reject(
- new Error(`Could not get duration for path: ${path}`)
+ new Error(`Could not get duration for path: ${audioPath}`)
);
}
} catch (err) {
@@ -137,12 +174,12 @@ export const Waveform = forwardRef((props, ref) => {
};
const getAudioWaveFormForPath = async (noOfSample: number) => {
- if (!isNil(path) && !isEmpty(path)) {
+ if (!isNil(audioPath) && !isEmpty(audioPath)) {
try {
(onChangeWaveformLoadState as Function)?.(true);
const result = await extractWaveformData({
- path: path,
- playerKey: `PlayerFor${path}`,
+ path: audioPath,
+ playerKey: `PlayerFor${audioPath}`,
noOfSamples: noOfSample,
});
(onChangeWaveformLoadState as Function)?.(false);
@@ -161,9 +198,11 @@ export const Waveform = forwardRef((props, ref) => {
}
} else {
(onError as Function)(
- `Can not find waveform for mode ${mode} path: ${path}`
+ `Can not find waveform for mode ${mode} path: ${audioPath}`
+ );
+ console.error(
+ `Can not find waveform for mode ${mode} path: ${audioPath}`
);
- console.error(`Can not find waveform for mode ${mode} path: ${path}`);
}
};
@@ -171,7 +210,7 @@ export const Waveform = forwardRef((props, ref) => {
if (mode === 'static') {
try {
const result = await stopPlayer({
- playerKey: `PlayerFor${path}`,
+ playerKey: `PlayerFor${audioPath}`,
});
await preparePlayerForPath();
if (!isNil(result) && result) {
@@ -180,7 +219,7 @@ export const Waveform = forwardRef((props, ref) => {
return Promise.resolve(result);
} else {
return Promise.reject(
- new Error(`error in stopping player for path: ${path}`)
+ new Error(`error in stopping player for path: ${audioPath}`)
);
}
} catch (err) {
@@ -198,8 +237,8 @@ export const Waveform = forwardRef((props, ref) => {
try {
const play = await playPlayer({
finishMode: FinishMode.stop,
- playerKey: `PlayerFor${path}`,
- path: path,
+ playerKey: `PlayerFor${audioPath}`,
+ path: audioPath,
...args,
});
@@ -208,7 +247,7 @@ export const Waveform = forwardRef((props, ref) => {
return Promise.resolve(true);
} else {
return Promise.reject(
- new Error(`error in starting player for path: ${path}`)
+ new Error(`error in starting player for path: ${audioPath}`)
);
}
} catch (error) {
@@ -225,14 +264,14 @@ export const Waveform = forwardRef((props, ref) => {
if (mode === 'static') {
try {
const pause = await pausePlayer({
- playerKey: `PlayerFor${path}`,
+ playerKey: `PlayerFor${audioPath}`,
});
if (pause) {
setPlayerState(PlayerState.paused);
return Promise.resolve(true);
} else {
return Promise.reject(
- new Error(`error in pause player for path: ${path}`)
+ new Error(`error in pause player for path: ${audioPath}`)
);
}
} catch (error) {
@@ -359,7 +398,7 @@ export const Waveform = forwardRef((props, ref) => {
};
useEffect(() => {
- if (!isNil(viewLayout?.width)) {
+ if (!isNil(viewLayout?.width) && audioPath !== undefined) {
const getNumberOfSamples = floor(
(viewLayout?.width ?? 0) / (candleWidth + candleSpace)
);
@@ -369,10 +408,10 @@ export const Waveform = forwardRef((props, ref) => {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [viewLayout, mode, candleWidth, candleSpace]);
+ }, [viewLayout, mode, candleWidth, candleSpace, audioPath]);
useEffect(() => {
- if (!isNil(seekPosition)) {
+ if (!isNil(seekPosition) && audioPath !== undefined) {
if (mode === 'static') {
const seekAmount =
(seekPosition?.pageX - (viewLayout?.x ?? 0)) /
@@ -381,7 +420,7 @@ export const Waveform = forwardRef((props, ref) => {
if (!panMoving) {
seekToPlayer({
- playerKey: `PlayerFor${path}`,
+ playerKey: `PlayerFor${audioPath}`,
progress: clampedSeekAmount * songDuration,
});
if (playerState === PlayerState.playing) {
@@ -393,25 +432,35 @@ export const Waveform = forwardRef((props, ref) => {
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [seekPosition, panMoving, mode, songDuration]);
+ }, [seekPosition, panMoving, mode, songDuration, audioPath]);
useEffect(() => {
- const tracePlayerState = onDidFinishPlayingAudio(async data => {
- if (data.playerKey === `PlayerFor${path}`) {
- if (data.finishType === FinishMode.stop) {
- setPlayerState(PlayerState.stopped);
- setCurrentProgress(0);
- await preparePlayerForPath();
+ if (audioPath !== undefined) {
+ const tracePlayerState = onDidFinishPlayingAudio(async data => {
+ if (data.playerKey === `PlayerFor${audioPath}`) {
+ if (data.finishType === FinishMode.stop) {
+ setPlayerState(PlayerState.stopped);
+ setCurrentProgress(0);
+ await preparePlayerForPath();
+ }
}
- }
- });
+ });
- const tracePlaybackValue = onCurrentDuration(data => {
- if (data.playerKey === `PlayerFor${path}`) {
- setCurrentProgress(data.currentDuration);
- }
- });
+ const tracePlaybackValue = onCurrentDuration(data => {
+ if (data.playerKey === `PlayerFor${audioPath}`) {
+ setCurrentProgress(data.currentDuration);
+ }
+ });
+ return () => {
+ tracePlayerState.remove();
+ tracePlaybackValue.remove();
+ };
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [audioPath]);
+
+ useEffect(() => {
const traceRecorderWaveformValue = onCurrentRecordingWaveformData(
result => {
if (mode === 'live') {
@@ -424,9 +473,8 @@ export const Waveform = forwardRef((props, ref) => {
}
}
);
+
return () => {
- tracePlayerState.remove();
- tracePlaybackValue.remove();
traceRecorderWaveformValue.remove();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -447,17 +495,19 @@ export const Waveform = forwardRef((props, ref) => {
}, [recorderState]);
useEffect(() => {
- if (panMoving) {
- if (playerState === PlayerState.playing) {
- pausePlayerAction();
- }
- } else {
- if (playerState === PlayerState.paused) {
- startPlayerAction();
+ if (audioPath !== undefined) {
+ if (panMoving) {
+ if (playerState === PlayerState.playing) {
+ pausePlayerAction();
+ }
+ } else {
+ if (playerState === PlayerState.paused) {
+ startPlayerAction();
+ }
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [panMoving]);
+ }, [panMoving, audioPath]);
const panResponder = useRef(
PanResponder.create({
diff --git a/src/components/Waveform/WaveformTypes.ts b/src/components/Waveform/WaveformTypes.ts
index 1a26154..9010f89 100644
--- a/src/components/Waveform/WaveformTypes.ts
+++ b/src/components/Waveform/WaveformTypes.ts
@@ -25,6 +25,9 @@ export interface StaticWaveform extends BaseWaveform {
songDuration: number
) => void;
onChangeWaveformLoadState?: (state: boolean) => void;
+ isExternalUrl?: boolean;
+ onDownloadStateChange?: (state: boolean) => void;
+ onDownloadProgressChange?: (currentProgress: number) => void;
}
export interface LiveWaveform extends BaseWaveform {