Multi-device (mobile phone) synchronization - React Native
P粉151466081
2023-09-05 11:58:38
<p>I'm trying to create a Lightshow that will launch on all users' mobile devices at the same time (can have up to 2000-3000 users). All users will use the Internet (Wi-Fi or mobile data). In our FE we use Apollo for subscriptions and it works like a charm. </p>
<p>But the problem is, user A can get subscription events faster than user B. This means user A will start playing the game earlier than user B, which is a problem because we have lighshow architecture and users can't start earlier or later. As a user, you'll see the difference immediately, and it doesn't look good. </p>
<p>What I see is that the latency difference can be up to 300ms. The time between android (Xiaomi 9) and ios (iPhone11) is mostly around 180ms -. If we compare two iOS devices iPhone 11 and iPhone 13, the difference is around 50-100 milliseconds. Is there a way to eliminate this difference, or at least reduce it to 40-60 ms for all devices? </p>
<p>Starting in BE, we send the server time (utc 0) and the light show will start with the timestamp=> In FE, I added 10 seconds to give all devices time to resolve the feature, so the startup is delayed -> "Timestamp 10 seconds".</p>
<p>In FE, I used the library "react-native-ntp-client" to get the same time on all types of devices (ios/android, because I found that the time is slightly different for each device). I then calculate the difference between "start" and "ntpTime" and provide that as a timeout to my setTimeout function, which will trigger the start of the light show. </p>
<p>Below I've provided an example of how I use the screen where the light show is located. </p>
<pre class="brush:php;toolbar:false;">import dayjs from 'dayjs';
import ntpClient from 'react-native-ntp-client';
export const LightshowScreen: React.FC<
LightshowScreenProps<'Lightshow'>
> = ({route}) => {
const {data, loading} = useSubscription(JOIN_LIGHTSHOW_SUBSCRIPTION, {
variables: {lightshowId: route.params.lightshow.id},
});
useEffect(() => {
const getServerTime = async (lightshowStartAt: number) => {
// Delay start 10s to provide enough time to finish our functions - lightshowStartAt is timestamp from our server
const lightshowStart = dayjs.unix(lightshowStartAt).add(10, 's');
ntpClient.getNetworkTime('time.cloudflare.com', 123, (error, date) => {
if (error) {
console.error('Cant connect', error);
return;
}
console.log('NTP client date - ', date); // Mon Jul 08 2013 21:31:31 GMT 0200 (Paris, Madrid (heure d’été))
let ntpTime = dayjs(date);
// Diff in ms
const diff = lightshowStart.diff(ntpTime);
// After this timeout all devices should start play at the same time
setTimeout(() => {
lightshowStartHandler();
}, diff);
});
};
if (data && data?.joinLightshow.started) {
const lightshowData = data.lightshow;
getServerTime(lightshowData.startedAt);
}
}, [data]);
useEffect(() => {
if (data && data?.joinLightshow?.finished) {
lightshowFinishHandler();
}
}, [data]);
return (
<View style={style.container}>
....
</View>
);
};</pre>
<p>Thanks for all your comments and thoughts;)</p>
What you want to do is really difficult. Synchronizing such a device is difficult. Doing this is nearly impossible when you don't own and control the hardware. Frankly, I don't think you'll ever get what you want.
Timestamps never work. For one, these devices won't all have the same timing. They will all be slightly off. Your next idea is to send them the time from a central source such as your server. The problem is that sending data to each device will take a different, random amount of time. You could try to guess the latency by pre-calculating the round-trip time for a dozen packets, but that's still a guess and may not be accurate for the next packet. NTP helps keep devices' times close to the same time, but not as precise as you want.
Even if it does achieve the accuracy you want - Android is not a real-time operating system. Not so with the iPhone. Even if you set your alarm for 12:00:00, it won't fire at exactly 12:00:00.000. Sometime after that, it will fire when the operating system has idle time, idle cores, and considers your application to be the most important scheduled application. This may take hundreds of milliseconds. There are operating systems that can give you the promise you want. Known as real-time operating systems, they are often used in embedded devices that cannot fail, such as medical equipment and controllers for expensive machines. They are a completely different approach to writing operating systems than those used by consumer devices.
I would really recommend rethinking your needs and being more realistic about them. There are technologies that allow you to get what you want, but not on random hardware on consumer operating systems over the Internet.
Also, if you want to do this - I really don't recommend using React Native, it runs the interpreter in a garbage collected language and has very random timing. You'll want to at least write your launcher in C, as this is the most predictable approach.
But really, please reconsider your needs. Why does it need to start within 50 milliseconds? When you do things over the internet, does it really matter if people are out of sync for a second?