fix: play sounds on iOS

This commit is contained in:
MAZE 2024-09-03 16:50:26 +03:30
parent ace0d6eecc
commit a64b30d047
3 changed files with 106 additions and 12 deletions

View file

@ -12,6 +12,7 @@ import { Categories } from '@/components/categories';
import { SharedModal } from '@/components/modals/shared';
import { Toolbar } from '@/components/toolbar';
import { SnackbarProvider } from '@/contexts/snackbar';
import { SoundProvider } from '@/contexts/sound';
import { sounds } from '@/data/sounds';
import { FADE_OUT } from '@/constants/events';
@ -86,17 +87,19 @@ export function App() {
}, [favoriteSounds, categories]);
return (
<SnackbarProvider>
<StoreConsumer>
<Container>
<div id="app" />
<Buttons />
<Categories categories={allCategories} />
</Container>
<SoundProvider>
<SnackbarProvider>
<StoreConsumer>
<Container>
<div id="app" />
<Buttons />
<Categories categories={allCategories} />
</Container>
<Toolbar />
<SharedModal />
</StoreConsumer>
</SnackbarProvider>
<Toolbar />
<SharedModal />
</StoreConsumer>
</SnackbarProvider>
</SoundProvider>
);
}

73
src/contexts/sound.tsx Normal file
View file

@ -0,0 +1,73 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Howler } from 'howler';
// Define the context's interface
interface SoundContextType {
connectBufferSource: (bufferSource: AudioBufferSourceNode) => void;
}
// Create the SoundContext with an empty initial value
const SoundContext = createContext<SoundContextType | undefined>(undefined);
// Custom hook to use the SoundContext
export const useSoundContext = (): SoundContextType => {
const context = useContext(SoundContext);
if (!context) {
throw new Error('useSoundContext must be used within a SoundProvider');
}
return context;
};
// Props for the SoundProvider component
interface SoundProviderProps {
children: React.ReactNode;
}
export const SoundProvider: React.FC<SoundProviderProps> = ({ children }) => {
const [dest, setDest] = useState<MediaStreamAudioDestinationNode | null>(
null,
);
const [audioTag, setAudioTag] = useState<HTMLAudioElement | null>(null);
useEffect(() => {
if (typeof window !== 'undefined') {
// Get the Howler.js AudioContext after the component is mounted
const audioCtx = Howler.ctx;
if (audioCtx) {
const mediaDest = audioCtx.createMediaStreamDestination();
setDest(mediaDest);
// Create an audio element to trick iOS
const audioElement = document.createElement('audio');
audioElement.srcObject = mediaDest.stream;
audioElement.style.display = 'none'; // Hide the audio element
document.body.appendChild(audioElement);
setAudioTag(audioElement);
return () => {
// Clean up the audio element on unmount
document.body.removeChild(audioElement);
};
}
}
}, []);
// Function to connect a buffer source to the MediaStreamDestination
const connectBufferSource = (bufferSource: AudioBufferSourceNode) => {
if (dest) {
bufferSource.connect(dest);
// Start playing the audio once the first buffer connects
if (audioTag && audioTag.paused) {
audioTag.play().catch(() => console.error('Failed to play audio'));
}
}
};
return (
<SoundContext.Provider value={{ connectBufferSource }}>
{children}
</SoundContext.Provider>
);
};

View file

@ -5,6 +5,7 @@ import { useLoadingStore } from '@/stores/loading';
import { subscribe } from '@/lib/event';
import { useSSR } from './use-ssr';
import { FADE_OUT } from '@/constants/events';
import { useSoundContext } from '@/contexts/sound';
/**
* A custom React hook to manage sound playback using Howler.js with additional features.
@ -34,6 +35,8 @@ export function useSound(
const setIsLoading = useLoadingStore(state => state.set);
const { isBrowser } = useSSR();
const { connectBufferSource } = useSoundContext(); // Access SoundContext
const sound = useMemo<Howl | null>(() => {
let sound: Howl | null = null;
@ -43,6 +46,14 @@ export function useSound(
onload: () => {
setIsLoading(src, false);
setHasLoaded(true);
// Connect the buffer source to the MediaStreamDestination
// @ts-ignore
const source = sound!._sounds[0]._node.bufferSource;
if (source) {
console.log('DOOOOPE');
connectBufferSource(source);
}
},
preload: options.preload ?? false,
src: src,
@ -50,7 +61,14 @@ export function useSound(
}
return sound;
}, [src, isBrowser, setIsLoading, html5, options.preload]);
}, [
src,
isBrowser,
setIsLoading,
html5,
options.preload,
connectBufferSource,
]);
useEffect(() => {
if (sound) {