'Expo av playbackObject's onPlaybackStatusUpdate is called only on play/stop instead of interval by progressUpdateIntervalMillis
onPlaybackStatusUpdate should return the output after every 100ms but only returns on play or stop. am I missing something in my code. (I am new to Expo and Expo AV). Please see code below used for loading and playing audio file
if(!sound._loaded)
await sound.loadAsync(
require('./assets/Audio.mp3'),
initialStatus,
);
sound.setStatusAsync({progressUpdateIntervalMillis: 200});
sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
await sound.playAsync();
console.log(sound);
}
const initialStatus = (status) => {
console.log("initialStatus");
console.log(status);
}
let onPlaybackStatusUpdate = async (playbackStatus) => {
//if(!isNaN(playbackStatus.durationMillis))
//setTotalLength(playbackStatus.durationMillis/1000);
//setCurrentPos(playbackStatus.positionMillis.toString().split(".")[0]/1000);
//setIsPlaying(playbackStatus.isPlaying);
console.log("onPlaybackStatusUpdate");
console.log(playbackStatus);
}
Solution 1:[1]
For sound instances create a ref
variable and use that to trigger pause/play and also Load.
Like this,
const sound = React.useRef(new Audio.Sound());
For Loading an audio,
const result = await sound.current.loadAsync(require('./test.mp3'), {}, true);
if (result.isLoaded === false) {
console.log('Error in Loading Audio');
} else {
// Audio is loaded do whatever needed
}
For pause
const result = await sound.current.getStatusAsync();
if (result.isLoaded) {
if (result.isPlaying === true) {
// sound.current.pauseAsync();
// Pause Here
}
}
For play,
const result = await sound.current.getStatusAsync();
if (result.isLoaded) {
if (result.isPlaying === false) {
// sound.current.playAsync();
// play Here
}
}
Solution 2:[2]
Found a fix for web.
The issue lies with the event emitter between react-native and react-native-web.
It's a small change between two files but I made a new .web.js file for Sound.js since ios and android were fine. The main difference is in
this._key.ontimeupdate
You'll have to patch expo-av.
I'm stil on sdk 42 so here's my patch for version 9.2.3 of expo-av that worked as expected.
diff --git a/node_modules/expo-av/build/Audio/Sound.web.js b/node_modules/expo-av/build/Audio/Sound.web.js
new file mode 100644
index 0000000..28f4460
--- /dev/null
+++ b/node_modules/expo-av/build/Audio/Sound.web.js
@@ -0,0 +1,162 @@
+import { EventEmitter } from '@unimodules/core';
+import { PlaybackMixin, assertStatusValuesInBounds, getNativeSourceAndFullInitialStatusForLoadAsync, getUnloadedStatus, } from '../AV';
+import ExponentAV from '../ExponentAV';
+import { throwIfAudioIsDisabled } from './AudioAvailability';
+export class Sound {
+ constructor() {
+ this._loaded = false;
+ this._loading = false;
+ this._key = null;
+ this._lastStatusUpdate = null;
+ this._lastStatusUpdateTime = null;
+ this._subscriptions = [];
+ this._eventEmitter = new EventEmitter(ExponentAV);
+ this._coalesceStatusUpdatesInMillis = 100;
+ this._onPlaybackStatusUpdate = null;
+ this._internalStatusUpdateCallback = ({ key, status, }) => {
+ if (this._key === key) {
+ this._callOnPlaybackStatusUpdateForNewStatus(status);
+ }
+ };
+ this._internalErrorCallback = ({ key, error }) => {
+ if (this._key === key) {
+ this._errorCallback(error);
+ }
+ };
+ this._errorCallback = (error) => {
+ this._clearSubscriptions();
+ this._loaded = false;
+ this._key = null;
+ this._callOnPlaybackStatusUpdateForNewStatus(getUnloadedStatus(error));
+ };
+ // ### Unified playback API ### (consistent with Video.js)
+ // All calls automatically call onPlaybackStatusUpdate as a side effect.
+ // Get status API
+ this.getStatusAsync = async () => {
+ if (this._loaded) {
+ return this._performOperationAndHandleStatusAsync(() => ExponentAV.getStatusForSound(this._key));
+ }
+ const status = getUnloadedStatus();
+ this._callOnPlaybackStatusUpdateForNewStatus(status);
+ return status;
+ };
+ }
+ // Internal methods
+ _callOnPlaybackStatusUpdateForNewStatus(status) {
+ const shouldDismissBasedOnCoalescing = this._lastStatusUpdateTime &&
+ JSON.stringify(status) === this._lastStatusUpdate &&
+ Date.now() - this._lastStatusUpdateTime.getTime() < this._coalesceStatusUpdatesInMillis;
+ if (this._onPlaybackStatusUpdate != null && !shouldDismissBasedOnCoalescing) {
+ this._onPlaybackStatusUpdate(status);
+ this._lastStatusUpdateTime = new Date();
+ this._lastStatusUpdate = JSON.stringify(status);
+ }
+ }
+ async _performOperationAndHandleStatusAsync(operation) {
+ throwIfAudioIsDisabled();
+ if (this._loaded) {
+ const status = await operation();
+ this._callOnPlaybackStatusUpdateForNewStatus(status);
+ return status;
+ }
+ else {
+ throw new Error('Cannot complete operation because sound is not loaded.');
+ }
+ }
+ // TODO: We can optimize by only using time observer on native if (this._onPlaybackStatusUpdate).
+ _subscribeToNativeEvents() {
+ if (this._loaded) {
+ this._subscriptions.push(this._eventEmitter.addListener('didUpdatePlaybackStatus', this._internalStatusUpdateCallback));
+ this._subscriptions.push(this._eventEmitter.addListener('ExponentAV.onError', this._internalErrorCallback));
+ }
+ }
+ _clearSubscriptions() {
+ this._subscriptions.forEach(e => e.remove());
+ this._subscriptions = [];
+ }
+ setOnPlaybackStatusUpdate(onPlaybackStatusUpdate) {
+ this._onPlaybackStatusUpdate = onPlaybackStatusUpdate;
+ this.getStatusAsync();
+ }
+ // Loading / unloading API
+ async loadAsync(source, initialStatus = {}, downloadFirst = true) {
+ throwIfAudioIsDisabled();
+ if (this._loading) {
+ throw new Error('The Sound is already loading.');
+ }
+ if (!this._loaded) {
+ this._loading = true;
+ const { nativeSource, fullInitialStatus, } = await getNativeSourceAndFullInitialStatusForLoadAsync(source, initialStatus, downloadFirst);
+ // This is a workaround, since using load with resolve / reject seems to not work.
+ return new Promise((resolve, reject) => {
+ const loadSuccess = (result) => {
+ const [key, status] = result;
+ this._key = key;
+ this._loaded = true;
+ this._loading = false;
+ this._subscribeToNativeEvents();
+ this._key.ontimeupdate = () => {
+ this._eventEmitter.emit('didUpdatePlaybackStatus', {
+ key,
+ status: ExponentAV.getStatus(this._key),
+ });
+ };
+ this._callOnPlaybackStatusUpdateForNewStatus(status);
+ resolve(status);
+ };
+ const loadError = (error) => {
+ this._loading = false;
+ reject(error);
+ };
+ ExponentAV.loadForSound(nativeSource, fullInitialStatus)
+ .then(loadSuccess)
+ .catch(loadError);
+ });
+ }
+ else {
+ throw new Error('The Sound is already loaded.');
+ }
+ }
+ async unloadAsync() {
+ if (this._loaded) {
+ this._loaded = false;
+ const key = this._key;
+ this._key = null;
+ const status = await ExponentAV.unloadForSound(key);
+ this._callOnPlaybackStatusUpdateForNewStatus(status);
+ this._clearSubscriptions();
+ return status;
+ }
+ else {
+ return this.getStatusAsync(); // Automatically calls onPlaybackStatusUpdate.
+ }
+ }
+ // Set status API (only available while isLoaded = true)
+ async setStatusAsync(status) {
+ assertStatusValuesInBounds(status);
+ return this._performOperationAndHandleStatusAsync(() => ExponentAV.setStatusForSound(this._key, status));
+ }
+ async replayAsync(status = {}) {
+ if (status.positionMillis && status.positionMillis !== 0) {
+ throw new Error('Requested position after replay has to be 0.');
+ }
+ return this._performOperationAndHandleStatusAsync(() => ExponentAV.replaySound(this._key, {
+ ...status,
+ positionMillis: 0,
+ shouldPlay: true,
+ }));
+ }
+}
+/** @deprecated Use `Sound.createAsync()` instead */
+Sound.create = async (source, initialStatus = {}, onPlaybackStatusUpdate = null, downloadFirst = true) => {
+ console.warn(`Sound.create is deprecated in favor of Sound.createAsync with the same API except for the new method name`);
+ return Sound.createAsync(source, initialStatus, onPlaybackStatusUpdate, downloadFirst);
+};
+Sound.createAsync = async (source, initialStatus = {}, onPlaybackStatusUpdate = null, downloadFirst = true) => {
+ const sound = new Sound();
+ sound.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
+ const status = await sound.loadAsync(source, initialStatus, downloadFirst);
+ return { sound, status };
+};
+Object.assign(Sound.prototype, PlaybackMixin);
+//# sourceMappingURL=Sound.js.map
\ No newline at end of file
diff --git a/node_modules/expo-av/build/ExponentAV.web.js b/node_modules/expo-av/build/ExponentAV.web.js
index 4aeec83..591510e 100644
--- a/node_modules/expo-av/build/ExponentAV.web.js
+++ b/node_modules/expo-av/build/ExponentAV.web.js
@@ -75,6 +75,9 @@ export default {
get name() {
return 'ExponentAV';
},
+ getStatus(element) {
+ return getStatusFromMedia(element);
+ },
async getStatusForVideo(element) {
return getStatusFromMedia(element);
},
Would've submitted a PR to expo but I'm still on version 42 and 45 is in beta. I looked around version 45 and major changes have happened since v43 even with the EvenEmitter logic I dug into to solve this.
Hope this helps someone out!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Kartikey |
Solution 2 |