diff --git a/src/assets/audio/musicland-entry.wav b/src/assets/audio/musicland-entry.wav
new file mode 100644
index 0000000..08735e2
Binary files /dev/null and b/src/assets/audio/musicland-entry.wav differ
diff --git a/src/components/EntryJingle.native.js b/src/components/EntryJingle.native.js
new file mode 100644
index 0000000..1c23e87
--- /dev/null
+++ b/src/components/EntryJingle.native.js
@@ -0,0 +1,37 @@
+import { useEffect, useRef } from 'react'
+import { useAudioPlayer } from 'expo-audio'
+
+const entryJingle = require('../assets/audio/musicland-entry.wav')
+
+const EntryJingle = ({ active }) => {
+ const player = useAudioPlayer(entryJingle)
+ const hasStartedRef = useRef(false)
+
+ useEffect(() => {
+ player.loop = false
+ player.volume = 0.32
+
+ if (!active) {
+ player.pause()
+ return undefined
+ }
+
+ if (!hasStartedRef.current) {
+ hasStartedRef.current = true
+ player
+ .seekTo(0)
+ .then(() => player.play())
+ .catch((error) => {
+ console.warn("EntryJingle: impossible de lancer le son d'entrée", error)
+ })
+ }
+
+ return () => {
+ player.pause()
+ }
+ }, [active, player])
+
+ return null
+}
+
+export default EntryJingle
diff --git a/src/components/EntryJingle.web.js b/src/components/EntryJingle.web.js
new file mode 100644
index 0000000..9cbf031
--- /dev/null
+++ b/src/components/EntryJingle.web.js
@@ -0,0 +1,60 @@
+import { Asset } from 'expo-asset'
+import { useEffect } from 'react'
+
+const entryJingle = require('../assets/audio/musicland-entry.wav')
+
+const EntryJingle = ({ active }) => {
+ useEffect(() => {
+ if (!active) {
+ return undefined
+ }
+
+ const audio = new Audio(Asset.fromModule(entryJingle).uri)
+ let isDisposed = false
+ let isWaitingForInteraction = false
+
+ audio.loop = false
+ audio.preload = 'auto'
+ audio.volume = 0.32
+
+ const removeInteractionListeners = () => {
+ document.removeEventListener('pointerdown', handleFirstInteraction)
+ document.removeEventListener('keydown', handleFirstInteraction)
+ isWaitingForInteraction = false
+ }
+
+ const play = () => {
+ audio
+ .play()
+ .then(removeInteractionListeners)
+ .catch(() => {
+ if (isDisposed || isWaitingForInteraction) {
+ return
+ }
+ isWaitingForInteraction = true
+ document.addEventListener('pointerdown', handleFirstInteraction, { once: true })
+ document.addEventListener('keydown', handleFirstInteraction, { once: true })
+ })
+ }
+
+ function handleFirstInteraction() {
+ if (!isDisposed) {
+ play()
+ }
+ }
+
+ play()
+
+ return () => {
+ isDisposed = true
+ removeInteractionListeners()
+ audio.pause()
+ audio.removeAttribute('src')
+ audio.load()
+ }
+ }, [active])
+
+ return null
+}
+
+export default EntryJingle
diff --git a/src/screens/LandingPage.js b/src/screens/LandingPage.js
index 24097b2..ba5c0a1 100644
--- a/src/screens/LandingPage.js
+++ b/src/screens/LandingPage.js
@@ -3,6 +3,7 @@ import { useNavigation } from '@react-navigation/native'
import { useCallback, useEffect, useState } from 'react'
import { Image, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import { background, icons } from '../assets'
+import EntryJingle from '../components/EntryJingle'
import FullscreenIntroVideo from '../components/FullscreenIntroVideo'
import GradientButton from '../components/GradientButton'
import { videosRef } from '../config/firebase'
@@ -21,6 +22,7 @@ export default function LandingPage() {
const { currentUID } = useUser()
const [videoUrl, setVideoUrl] = useState(null)
const [selectedLanguage, setSelectedLanguage] = useState(null)
+ const [hasLoadedPreferredLanguage, setHasLoadedPreferredLanguage] = useState(false)
const [showFreeCreditsPopup, setShowFreeCreditsPopup] = useState(false)
const isAuthenticated = !!currentUID
@@ -62,6 +64,11 @@ export default function LandingPage() {
.catch((error) => {
console.warn('LandingPage: failed to load language', error)
})
+ .finally(() => {
+ if (isMounted) {
+ setHasLoadedPreferredLanguage(true)
+ }
+ })
return () => {
isMounted = false
@@ -80,6 +87,7 @@ export default function LandingPage() {
return (
<>
+