Single instance of howler player in React/NextJS - javascript

I have many audios on every page my app render (in NextJS). I'd like that when an user clicks on an audio button any other audio playing stops (I want to have just one audio playing at the time). How can I achieve this? (I'm using Howler)
My idea was to have a single instance of Howler as singleton it? is it possible to have a single instance of a function and share it across the app? with React Context? (but they're not unique, right?)

Why don't you create a React context and store the Howler instance in there?
https://reactjs.org/docs/context.html
export default ({ children }) => {
const [howler, setHowler] = useState(new Howl({ ... }));
return (
<HowlerContext.Provider value={howler}>
{children}
</AmplitudeContext.Provider>
);
};
This way you can share it with every child to the provider

Related

Class works fine normally but stops working when instantiated

I'm making a bluetooth remote in React Native. I have a class BLE that works on it's own, but parts of it don't work when I it in another class.
import BLE from './Core/BLE.js'
const myBLE = new BLE();
function DebugScreen(){
useEffect(() => {
myBLE.componentDidMount();
}, []);
return(
<ScrollView>
<Text>State: {myBLE.state.info}</Text>
<Text>Devices: {JSON.stringify(myBLE.state.ble_devices)}</Text>
</ScrollView>
)
}
export default DebugScreen;
The devices text box shows data just fine, but state does not. I verified that this was not simply the state not refreshing by putting a timer on the screen.
My question is, is there a fundamental difference between code running in an instantiated class and one that isn't?
I figured out what I was doing wrong. Instantiating classes the normal JS way is bad practice in React/React Native. To have top level code and information, I need to use context https://reactjs.org/docs/context.html

Next steps in a react/redux sound dispatch app

I'm building a react/redux drum machine app for a freeCodeCamp challenge, and I've got the buttons done and the connections made for onClick to dispatch an action with the url link to the sound as a property, but I'm hung up on what I need to create as a reducer for this to work how I'm wanting.
You can view what I've got so far here: https://codesandbox.io/s/k29r3928z7 , and you can ignore the pieces for the Hello/Goodbye switch at the top as that was just for me to understand the flow of React/Redux to start. My main question is what should I be doing with state in this case? Pressing the button won't really change the state at all, so I'm not sure what I should be doing at the 'new state' step of creating a reducer.
I am not familiar with the specific goals of the freeCodeCamp challenge, however if you're wanting the app to play the correct sound when the user clicks any one of the buttons shown, you can achieve this by updating your drumClickHit action as follows:
export const drumClickHit = url => {
//Create an audio object from the url provided to the action
const sound = new Audio(url);
//Play the audio object immediatly
sound.play();
return {
type: "CLICKHIT",
url
};
};
To illustrate the relationship between action creators and reducers, you could achieve the same result as above, by adding the following "CLICKHIT" case to your reducer as follows:
case "CLICKHIT": {
//Create an audio object from the url provided to the action.
//Get the url from the action
const sound = new Audio(action.url);
//Play the audio object immediatly
sound.play();
return state;
}
In the case of your app however, logic for audio playback is better placed in the action, rather than reducer.

Javascript Video loadmetadata event not being triggered on React-Redux Application

I have a React-Redux application that reads some metadata from videos.
However the code added to the loadmetadata event is never triggered.
As a workaround I have added a timer to wait 1 second before, which is a pretty bad solution and doesn't work every time.
Another thing is that I couldn't find an elegant way to integrate the video element into Redux code without having to manipulate the DOM.
The code looks like this:
videoPlayerElement = document.getElementById(`videoplayer-${videoId}`);
videoPlayerElement.addEventListener('loadedmetadata', function(e) {
const duration = videoPlayerElement.duration;
...
})
The code inside the listener is never executed.
I also have tried different ways to assign the loadmetadata event, i.e: assigning directly to videoPlayerElement.onloadmetadata still not working.
I thought it might be because of the scope of the object, so I changed it to a global just for testing... didn't help.
Any other idea about what might be causing?
If I run a simple example, like this one it works fine.
In react you should use synthetic events where possible. Example for your use case:
class MediaPlayer extends Component {
handleMetadata = event => {
const duration = event.currentTarget.duration;
// ...
}
render() {
const {src} = this.props;
return(
<video src={src} onLoadedMetadata={this.handleMetadata} />
);
}
}

dynamically update device info react native

I am using react-native-device-info to get DeviceLocale or DeviceCountry. But is there a way that I can update Device-info with out restarting the app.
For Example, on device settings If my language is set to "English(US)", Device-info cam detect that but if I change the settings to "English(CA)",Device-Info is not able to capture that until I restart the app.
Any help is really appreciated.
How are you changing the settings of the language?
If it's built in...you can do something like setting the language as a state of the component and once call the function to change the language settings, you can setState and it'll update the component with the new value.
If you're talking about hopping out of the app and manually changing the language settings then maybe setting creating a setInterval to check the device-info will work.
Assuming you're defining the reference to DeviceInfo outside of the component's class definition, you could try moving it inside like this.
class MyComponent extends React.Component {
checkDeviceInfo() {
let DeviceInfo = require('react-native-device-info');
let locale = DeviceInfo.getDeviceLocale();
// do something with locale
}
render() {
return (
<TouchableOpacity onPress={this.checkDeviceInfo.bind(this)}>
<Text>Touch Here</Text>
</TouchableOpacity>
)
}
}
You could put this in your component so that it's called whenever you want to check for DeviceInfo changes.

Redux architecture for a non-ui application

I am working on an html5 audio player and I am trying to figure out if Redux would be a good solution even without a UI for the state (data) to flow in.
I will have multiple modules:
Playback Module: Encapsulate all callbacks from the Audio Element and dispatch an action after each callback to update the state.
Streaming Module: All the logic to figure out which segment is need at a given time, which segment to prefetch, ...
MediaSource Module: The media source module is wrapper of all actions of the media source and the source buffer.
Question 1:
the Audio Element has it's own state, playing, paused, seeking, currentTime, ... and the Redux state reflects the state of the Audio Element.
Is that a good practice? I feel a bit concerned about having 2 states in different places and out of sync state...
Question 2:
Who is updated first?
Let's imagine I want to Pause my player:
//Implementation 1:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'}).then(()=> {
this.audio.paused = true;
});
}
// Implementation 2:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'});
this.audio.paused = true;
}
// Implementation 3:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'});
onPause();
}
function onPause() {
const state = getState();
if (this.audio.paused != state.paused) {
this.audio.paused = state.paused;
}
}
Question 3:
Every 0.5 second, the Audio element triggers a callback with a new current time. When this callback is trigger, I dispatch an action to update the current time in the state.
Now, the Streaming Module needs to be aware of that change in order to figure out which segment is needed at this given time and needed to know if we need to prefetch future segments.
How is my Streaming Module supposed to be aware of that state change?
Like that?:
currentTime = 0;
...
const state = getState();
if (state.currentTime !== currentTime){
// Do Something...
// Do Something Else...
}
Thank you
Let me try to tackle your questions one by one:
Question 1
Assuming that you are using the HTML5 audio component: there is nothing wrong about a redux state augmenting/syncing with a state of another component such as your audio player. In all cases, though, I suggest that the redux state follows the audio components state (should there be a delay for example)
If, however, you built the audio component yourself, maybe you can wire it into the redux ecosystem by dispatching events from it directly.
Question 2
Like I said in the previous answer, I would always go with pausing the actual component first, THEN update the state once the component acknowledges the pause. Code wise, this would mean triggering a dispatch when the audio component has fully paused (on an onPause callback for example). The reason for following the audio element in terms of state rather than expecting it to follow our state is because the element might not pause synchronously/immediately which could bring our states out of sync.
Question 3
When you dispatch the event to update the redux state every 0.5s, you could dispatch another event to your streaming module. For this, I suggest using an asynchronous action creator like so:
import StreamingModule from 'your-streaming-module';
export function updateTime (timeInMs) {
return (dispatch) => {
dispatch({
type: YOUR_UPDATE_ACTION
time: timeInMs
});
// pseudo
StreamingModule.fetchSegment(timeInMs);
};
}
An alternative would be to subscribe to the store state directly. This all depends on how your streaming module is structured/instantiated:
import { createStore } from 'redux';
import StreamingModule from 'your-streaming-module';
const store = createStore(...);
store.subscribe(() => {
StreamingModule.fetchSegment(store.getState().time.ms);
});
The latter might not work well unless you save the current time somewhere before you fetch the segment.
Hope this helps.

Categories

Resources