made mute button global

This commit is contained in:
Dmitri 2026-06-15 11:59:44 +02:00
parent 9d7e6f6cd2
commit 7a72117b6a
4 changed files with 97 additions and 67 deletions

View File

@ -45,6 +45,7 @@ import { usePageNavigationState } from '../hooks/usePageNavigationState';
import { useTransitionPlayback } from '../hooks/useTransitionPlayback';
import { useNetworkAware } from '../hooks/useNetworkAware';
import { resolveAssetPlaybackUrl } from '../lib/assetUrl';
import { presentationHasAudio } from '../lib/presentationAudio';
import { downloadManager } from '../lib/offline/DownloadManager';
import { isSafari } from '../lib/browserUtils';
import { logger } from '../lib/logger';
@ -817,39 +818,9 @@ export default function RuntimePresentation({
selectedPage?.background_audio_end_time != null
? parseFloat(String(selectedPage.background_audio_end_time))
: null;
const hasElementAudio = useMemo(
() =>
pageElements.some((element) => {
if (element.hoverAudioUrl || element.clickAudioUrl) return true;
if (
(element.type === 'audio_player' ||
element.type === 'video_player') &&
element.mediaUrl &&
!element.mediaMuted
) {
return true;
}
if (
element.galleryCards?.some(
(card: GalleryCarouselMediaItem) =>
card.mediaType === 'video' || Boolean(card.videoUrl),
)
) {
return true;
}
if (
element.infoPanelSections?.some((section) =>
section.images?.some(
(item: InfoPanelImage) =>
item.itemType === 'video' || Boolean(item.videoUrl),
),
)
) {
return true;
}
return false;
}),
[pageElements],
const hasPresentationAudio = useMemo(
() => presentationHasAudio(pages),
[pages],
);
// Global sound control starts muted for browser autoplay compatibility.
@ -857,7 +828,7 @@ export default function RuntimePresentation({
pageHasSound: pageVideoMuted === false,
hasBackgroundVideo: Boolean(backgroundVideoUrl),
hasBackgroundAudio: Boolean(backgroundAudioUrl),
hasElementAudio,
hasPresentationAudio,
});
// Note: useBackgroundVideoPlayback is handled internally by CanvasBackground component

View File

@ -24,6 +24,8 @@ export interface UseVideoSoundControlOptions {
hasBackgroundAudio?: boolean;
/** Whether page elements have hover/click audio effects or media player sound */
hasElementAudio?: boolean;
/** Whether any page in the presentation can produce sound */
hasPresentationAudio?: boolean;
}
export interface UseVideoSoundControlResult {
@ -61,6 +63,7 @@ export function useVideoSoundControl({
hasBackgroundVideo,
hasBackgroundAudio = false,
hasElementAudio = false,
hasPresentationAudio = false,
}: UseVideoSoundControlOptions): UseVideoSoundControlResult {
const { isMuted } = useGlobalAudioMute();
@ -71,6 +74,7 @@ export function useVideoSoundControl({
return {
isMuted,
showSoundButton:
hasPresentationAudio ||
(pageHasSound && hasBackgroundVideo) ||
hasBackgroundAudio ||
hasElementAudio,

View File

@ -0,0 +1,75 @@
import type { CanvasElement, ConstructorSchema } from '../types/constructor';
import type { TourPage } from '../types/entities';
import { parseJsonObject } from './parseJson';
type PageWithAudioFields = Pick<
TourPage,
| 'background_audio_url'
| 'background_video_url'
| 'background_video_muted'
| 'ui_schema_json'
>;
const asRecords = (value: unknown): Record<string, unknown>[] =>
Array.isArray(value)
? value.filter(
(item): item is Record<string, unknown> =>
Boolean(item) && typeof item === 'object',
)
: [];
const elementHasAudio = (element: Partial<CanvasElement>): boolean => {
if (element.hoverAudioUrl || element.clickAudioUrl) return true;
if (
(element.type === 'audio_player' || element.type === 'video_player') &&
element.mediaUrl &&
!element.mediaMuted
) {
return true;
}
const galleryCards = asRecords(element.galleryCards);
if (
galleryCards.some(
(card) => card.mediaType === 'video' || Boolean(card.videoUrl),
)
) {
return true;
}
const infoPanelSections = asRecords(element.infoPanelSections);
if (
infoPanelSections.some((section) =>
asRecords(section.images).some(
(item) => item.itemType === 'video' || Boolean(item.videoUrl),
),
)
) {
return true;
}
return false;
};
export const elementsHaveAudio = (
elements: Array<Partial<CanvasElement>> = [],
): boolean => elements.some(elementHasAudio);
export const pageHasAudio = (page: PageWithAudioFields): boolean => {
if (page.background_audio_url) return true;
if (page.background_video_url && page.background_video_muted === false) {
return true;
}
const schema = parseJsonObject<ConstructorSchema>(
page.ui_schema_json,
{} as ConstructorSchema,
);
return Array.isArray(schema.elements) && elementsHaveAudio(schema.elements);
};
export const presentationHasAudio = (
pages: PageWithAudioFields[] = [],
extraElements: Array<Partial<CanvasElement>> = [],
): boolean => pages.some(pageHasAudio) || elementsHaveAudio(extraElements);

View File

@ -48,6 +48,7 @@ import { backgroundAudioController } from '../lib/backgroundAudioController';
import { isSafari } from '../lib/browserUtils';
import { resolveAssetPlaybackUrl } from '../lib/assetUrl';
import { downloadManager } from '../lib/offline/DownloadManager';
import { presentationHasAudio } from '../lib/presentationAudio';
import { parseJsonObject } from '../lib/parseJson';
import {
resolveNavigationTarget,
@ -366,39 +367,18 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
[elements],
);
const hasElementAudio = useMemo(
const hasPresentationAudio = useMemo(
() =>
elements.some((element) => {
if (element.hoverAudioUrl || element.clickAudioUrl) return true;
if (
(element.type === 'audio_player' ||
element.type === 'video_player') &&
element.mediaUrl &&
!element.mediaMuted
) {
return true;
}
if (
element.galleryCards?.some(
(card: GalleryCarouselMediaItem) =>
card.mediaType === 'video' || Boolean(card.videoUrl),
)
) {
return true;
}
if (
element.infoPanelSections?.some((section) =>
section.images?.some(
(item: InfoPanelImage) =>
item.itemType === 'video' || Boolean(item.videoUrl),
),
)
) {
return true;
}
return false;
}),
[elements],
presentationHasAudio(pages, elements) ||
Boolean(backgroundAudioUrl) ||
Boolean(backgroundVideoUrl && backgroundVideoMuted === false),
[
pages,
elements,
backgroundAudioUrl,
backgroundVideoUrl,
backgroundVideoMuted,
],
);
// Global sound control starts muted for browser autoplay compatibility.
@ -406,7 +386,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
pageHasSound: backgroundVideoMuted === false,
hasBackgroundVideo: Boolean(backgroundVideoUrl),
hasBackgroundAudio: Boolean(backgroundAudioUrl),
hasElementAudio,
hasPresentationAudio,
});
// Look up current element for gallery carousel (so it receives updates from element editor)