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 ( <> +