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 { loadUser, loadUserId } from "@/entities/user/loaders";
|
||||||
import { routing } from "@/i18n/routing";
|
import { routing } from "@/i18n/routing";
|
||||||
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
import { AppUiStoreProvider } from "@/providers/app-ui-store-provider";
|
||||||
|
import { AudioProvider } from "@/providers/audio-provider";
|
||||||
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
import { ChatsInitializationProvider } from "@/providers/chats-initialization-provider";
|
||||||
import { ChatsProvider } from "@/providers/chats-provider";
|
import { ChatsProvider } from "@/providers/chats-provider";
|
||||||
import { RetainingStoreProvider } from "@/providers/retaining-store-provider";
|
import { RetainingStoreProvider } from "@/providers/retaining-store-provider";
|
||||||
@ -72,13 +73,15 @@ export default async function RootLayout({
|
|||||||
<UserProvider user={user}>
|
<UserProvider user={user}>
|
||||||
<SocketProvider userId={userId}>
|
<SocketProvider userId={userId}>
|
||||||
<RetainingStoreProvider>
|
<RetainingStoreProvider>
|
||||||
<ChatsInitializationProvider>
|
<AudioProvider>
|
||||||
<ChatsProvider initialChats={chats}>
|
<ChatsInitializationProvider>
|
||||||
<ToastProvider maxVisible={3}>
|
<ChatsProvider initialChats={chats}>
|
||||||
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
<ToastProvider maxVisible={3}>
|
||||||
</ToastProvider>
|
<AppUiStoreProvider>{children}</AppUiStoreProvider>
|
||||||
</ChatsProvider>
|
</ToastProvider>
|
||||||
</ChatsInitializationProvider>
|
</ChatsProvider>
|
||||||
|
</ChatsInitializationProvider>
|
||||||
|
</AudioProvider>
|
||||||
</RetainingStoreProvider>
|
</RetainingStoreProvider>
|
||||||
</SocketProvider>
|
</SocketProvider>
|
||||||
</UserProvider>
|
</UserProvider>
|
||||||
|
|||||||
@ -2,35 +2,48 @@
|
|||||||
|
|
||||||
import { useCallback, useEffect, useMemo } from "react";
|
import { useCallback, useEffect, useMemo } from "react";
|
||||||
|
|
||||||
import { audioService } from "@/services/audio";
|
export const useAudio = () => {
|
||||||
import { AudioUrls } from "@/shared/constants/audio";
|
const audio = useMemo(() => {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
interface UseAudioOptions {
|
const audioElement = new Audio("/audio/notification-new-message-1.wav");
|
||||||
enabled?: boolean;
|
audioElement.preload = "auto";
|
||||||
preload?: AudioUrls[];
|
return audioElement;
|
||||||
}
|
|
||||||
|
|
||||||
export const useAudio = (options: UseAudioOptions = {}) => {
|
|
||||||
const { enabled = true, preload = [] } = options;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
audioService.setEnabled(enabled);
|
|
||||||
}, [enabled]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (preload.length > 0) {
|
|
||||||
audioService.preload(preload);
|
|
||||||
}
|
}
|
||||||
}, [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(() => {
|
const playNewMessageNotification = useCallback(() => {
|
||||||
audioService.play(AudioUrls.NewMessage);
|
if (!audio) return;
|
||||||
}, []);
|
audio.currentTime = 0;
|
||||||
|
audio.play();
|
||||||
|
}, [audio]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
playNewMessageNotification,
|
playNewMessageNotification,
|
||||||
isEnabled: audioService.isAudioEnabled(),
|
|
||||||
}),
|
}),
|
||||||
[playNewMessageNotification]
|
[playNewMessageNotification]
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,9 +3,8 @@
|
|||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
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";
|
import { useSocketEvent } from "../socket/useSocketEvent";
|
||||||
|
|
||||||
export interface UseChatsSocketOptions {
|
export interface UseChatsSocketOptions {
|
||||||
@ -14,8 +13,6 @@ export interface UseChatsSocketOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
||||||
const { enableNotificationSound = true } = options;
|
|
||||||
|
|
||||||
const initialChats = options.initialChats ?? {
|
const initialChats = options.initialChats ?? {
|
||||||
categorizedChats: {},
|
categorizedChats: {},
|
||||||
startedChats: [],
|
startedChats: [],
|
||||||
@ -23,10 +20,7 @@ export const useChatsSocket = (options: UseChatsSocketOptions = {}) => {
|
|||||||
totalUnreadCount: 0,
|
totalUnreadCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { playNewMessageNotification } = useAudio({
|
const { playNewMessageNotification } = useAudioContext();
|
||||||
enabled: enableNotificationSound,
|
|
||||||
preload: [AudioUrls.NewMessage],
|
|
||||||
});
|
|
||||||
|
|
||||||
const [isInChat, setIsInChat] = useState(false);
|
const [isInChat, setIsInChat] = useState(false);
|
||||||
const [chats, setChats] = useState<IGetChatsListResponse>(initialChats);
|
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