notification-sound
fix sound on ios
This commit is contained in:
parent
d1fe43463e
commit
62cfba2d5d
7
global.d.ts
vendored
Normal file
7
global.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
declare global {
|
||||
interface Window {
|
||||
webkitAudioContext: typeof AudioContext;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
BIN
public/audio/notification-new-message-1.wav
Normal file
BIN
public/audio/notification-new-message-1.wav
Normal file
Binary file not shown.
@ -14,6 +14,7 @@ import { loadChatsList } from "@/entities/chats/loaders";
|
||||
import { loadUser, loadUserId } from "@/entities/user/loaders";
|
||||
import { routing } from "@/i18n/routing";
|
||||
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
||||
import { AudioProvider } from "@/providers/audio-provider";
|
||||
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
||||
import { ChatsProvider } from "@/providers/chats-provider";
|
||||
import { RetainingStoreProvider } from "@/providers/retaining-store-provider";
|
||||
@ -72,13 +73,15 @@ export default async function RootLayout({
|
||||
<UserProvider user={user}>
|
||||
<SocketProvider userId={userId}>
|
||||
<RetainingStoreProvider>
|
||||
<ChatsInitializationProvider>
|
||||
<ChatsProvider initialChats={chats}>
|
||||
<ToastProvider maxVisible={3}>
|
||||
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
||||
</ToastProvider>
|
||||
</ChatsProvider>
|
||||
</ChatsInitializationProvider>
|
||||
<AudioProvider>
|
||||
<ChatsInitializationProvider>
|
||||
<ChatsProvider initialChats={chats}>
|
||||
<ToastProvider maxVisible={3}>
|
||||
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
||||
</ToastProvider>
|
||||
</ChatsProvider>
|
||||
</ChatsInitializationProvider>
|
||||
</AudioProvider>
|
||||
</RetainingStoreProvider>
|
||||
</SocketProvider>
|
||||
</UserProvider>
|
||||
|
||||
@ -2,35 +2,48 @@
|
||||
|
||||
import { useCallback, useEffect, useMemo } from "react";
|
||||
|
||||
import { audioService } from "@/services/audio";
|
||||
import { AudioUrls } from "@/shared/constants/audio";
|
||||
|
||||
interface UseAudioOptions {
|
||||
enabled?: boolean;
|
||||
preload?: AudioUrls[];
|
||||
}
|
||||
|
||||
export const useAudio = (options: UseAudioOptions = {}) => {
|
||||
const { enabled = true, preload = [] } = options;
|
||||
|
||||
useEffect(() => {
|
||||
audioService.setEnabled(enabled);
|
||||
}, [enabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (preload.length > 0) {
|
||||
audioService.preload(preload);
|
||||
export const useAudio = () => {
|
||||
const audio = useMemo(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
const audioElement = new Audio("/audio/notification-new-message-1.wav");
|
||||
audioElement.preload = "auto";
|
||||
return audioElement;
|
||||
}
|
||||
}, [preload]);
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
const _audioContext = useMemo(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
return new (window.AudioContext || window.webkitAudioContext)();
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!audio) return;
|
||||
|
||||
const handleClick = () => {
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
setTimeout(() => {
|
||||
audio.pause();
|
||||
}, 10);
|
||||
};
|
||||
document.addEventListener("click", handleClick, { once: true });
|
||||
return () => {
|
||||
document.removeEventListener("click", handleClick);
|
||||
};
|
||||
}, [audio]);
|
||||
|
||||
const playNewMessageNotification = useCallback(() => {
|
||||
audioService.play(AudioUrls.NewMessage);
|
||||
}, []);
|
||||
if (!audio) return;
|
||||
audio.currentTime = 0;
|
||||
audio.play();
|
||||
}, [audio]);
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
playNewMessageNotification,
|
||||
isEnabled: audioService.isAudioEnabled(),
|
||||
}),
|
||||
[playNewMessageNotification]
|
||||
);
|
||||
|
||||
@ -3,9 +3,8 @@
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
||||
import { AudioUrls } from "@/shared/constants/audio";
|
||||
import { useAudioContext } from "@/providers/audio-provider";
|
||||
|
||||
import { useAudio } from "../audio/useAudio";
|
||||
import { useSocketEvent } from "../socket/useSocketEvent";
|
||||
|
||||
export interface UseChatsSocketOptions {
|
||||
@ -14,8 +13,6 @@ export interface UseChatsSocketOptions {
|
||||
}
|
||||
|
||||
export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
||||
const { enableNotificationSound = true } = options;
|
||||
|
||||
const initialChats = options.initialChats ?? {
|
||||
categorizedChats: {},
|
||||
startedChats: [],
|
||||
@ -23,10 +20,7 @@ export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
||||
totalUnreadCount: 0,
|
||||
};
|
||||
|
||||
const { playNewMessageNotification } = useAudio({
|
||||
enabled: enableNotificationSound,
|
||||
preload: [AudioUrls.NewMessage],
|
||||
});
|
||||
const { playNewMessageNotification } = useAudioContext();
|
||||
|
||||
const [isInChat, setIsInChat] = useState(false);
|
||||
const [chats, setChats] = useState<IGetChatsListResponse>(initialChats);
|
||||
|
||||
29
src/providers/audio-provider.tsx
Normal file
29
src/providers/audio-provider.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { createContext, ReactNode, useContext } from "react";
|
||||
|
||||
import { useAudio } from "@/hooks/audio/useAudio";
|
||||
|
||||
type AudioContextValue = ReturnType<typeof useAudio>;
|
||||
|
||||
const AudioContext = createContext<AudioContextValue | null>(null);
|
||||
|
||||
export function useAudioContext() {
|
||||
const ctx = useContext(AudioContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useAudio must be used within <AudioProvider>");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
interface AudioProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function AudioProvider({ children }: AudioProviderProps) {
|
||||
const value = useAudio();
|
||||
|
||||
return (
|
||||
<AudioContext.Provider value={value}>{children}</AudioContext.Provider>
|
||||
);
|
||||
}
|
||||
@ -1,58 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { AudioUrls, getAudioUrl } from "@/shared/constants/audio";
|
||||
|
||||
class AudioService {
|
||||
private audioElements = new Map<string, HTMLAudioElement>();
|
||||
private isEnabled = true;
|
||||
|
||||
private getAudioElement(url: string): HTMLAudioElement | undefined {
|
||||
if (!this.audioElements.has(url)) {
|
||||
const audio = new Audio(url);
|
||||
audio.preload = "auto";
|
||||
this.audioElements.set(url, audio);
|
||||
}
|
||||
return this.audioElements.get(url);
|
||||
}
|
||||
|
||||
play(key: AudioUrls): Promise<void> {
|
||||
if (!this.isEnabled) return Promise.resolve();
|
||||
|
||||
try {
|
||||
const audio = this.getAudioElement(getAudioUrl(key));
|
||||
if (audio) {
|
||||
audio.currentTime = 0;
|
||||
return audio.play();
|
||||
}
|
||||
return Promise.resolve();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("Failed to play audio:", error);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
setEnabled(enabled: boolean): void {
|
||||
this.isEnabled = enabled;
|
||||
}
|
||||
|
||||
isAudioEnabled(): boolean {
|
||||
return this.isEnabled;
|
||||
}
|
||||
|
||||
preload(keys: AudioUrls[]): void {
|
||||
keys.forEach(key => {
|
||||
this.getAudioElement(getAudioUrl(key));
|
||||
});
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.audioElements.forEach(audio => {
|
||||
audio.pause();
|
||||
audio.src = "";
|
||||
});
|
||||
this.audioElements.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export const audioService = new AudioService();
|
||||
@ -1,9 +0,0 @@
|
||||
const audioUrls: Record<AudioUrls, string> = {
|
||||
"new-message": "/audio/notification-new-message-1.mp3",
|
||||
};
|
||||
|
||||
export enum AudioUrls {
|
||||
NewMessage = "new-message",
|
||||
}
|
||||
|
||||
export const getAudioUrl = (key: AudioUrls) => audioUrls[key];
|
||||
Loading…
Reference in New Issue
Block a user