diff --git a/fluxer_app/src/features/channel/components/embeds/media/EmbedYouTube.tsx b/fluxer_app/src/features/channel/components/embeds/media/EmbedYouTube.tsx index c7da395af..a62ad808d 100644 --- a/fluxer_app/src/features/channel/components/embeds/media/EmbedYouTube.tsx +++ b/fluxer_app/src/features/channel/components/embeds/media/EmbedYouTube.tsx @@ -5,6 +5,7 @@ import {YOUTUBE_PROVIDER_NAME} from '@app/features/app/config/I18nDisplayConstan import styles from '@app/features/channel/components/embeds/media/EmbedYouTube.module.css'; import {OverlayActionButton, OverlayPlayButton} from '@app/features/channel/components/embeds/media/MediaButtons'; import {useNearViewport} from '@app/features/messaging/hooks/useNearViewport'; +import ActiveIframeEmbed from '@app/features/messaging/state/ActiveIframeEmbed'; import {openExternalUrlWithWarning} from '@app/features/messaging/utils/ExternalLinkUtils'; import * as ImageCacheUtils from '@app/features/messaging/utils/ImageCacheUtils'; import { @@ -21,7 +22,7 @@ import {useLingui} from '@lingui/react/macro'; import {ArrowSquareOutIcon, PlayIcon} from '@phosphor-icons/react'; import {AnimatePresence, motion} from 'framer-motion'; import {observer} from 'mobx-react-lite'; -import {type FC, useCallback, useEffect, useMemo, useState} from 'react'; +import {type FC, useCallback, useEffect, useId, useMemo, useState} from 'react'; const PLAY_VIDEO_DESCRIPTOR = msg({ message: 'Play video', @@ -147,7 +148,8 @@ const Thumbnail: FC = observer( ); export const EmbedYouTube: FC = observer(({embed, width = YOUTUBE_CONFIG.DEFAULT_WIDTH}) => { const {i18n} = useLingui(); - const [hasInteracted, setHasInteracted] = useState(false); + const embedId = useId(); + const hasInteracted = ActiveIframeEmbed.isActive(embedId); const posterSrc = embed.thumbnail?.proxy_url || ''; const {ref: visibilityRef, isNearViewport} = useNearViewport({rememberKey: posterSrc}); const [posterCacheAtMount] = useState(() => ({src: posterSrc, cached: ImageCacheUtils.hasImage(posterSrc)})); @@ -171,10 +173,14 @@ export const EmbedYouTube: FC = observer(({embed, width = YOU ); return cleanup; }, [isNearViewport, loadedPosterSrc, posterSrc]); - const handleInitialPlay = useCallback((event: React.MouseEvent | React.KeyboardEvent) => { - event.stopPropagation(); - setHasInteracted(true); - }, []); + const handleInitialPlay = useCallback( + (event: React.MouseEvent | React.KeyboardEvent) => { + event.stopPropagation(); + ActiveIframeEmbed.claim(embedId); + }, + [embedId], + ); + useEffect(() => () => ActiveIframeEmbed.release(embedId), [embedId]); const handleOpenInNewTab = useCallback( (event: React.MouseEvent | React.KeyboardEvent) => { event.stopPropagation(); @@ -250,6 +256,7 @@ export const EmbedYouTube: FC = observer(({embed, width = YOU sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts" src={embedVideoUrl.toString()} className={styles.iframe} + scrolling="no" data-embed-media="true" aria-label={embed.title || i18n._(VIDEO_DESCRIPTOR, {youtubeProviderName: YOUTUBE_PROVIDER_NAME})} data-flx="channel.embeds.media.embed-you-tube.iframe" diff --git a/fluxer_app/src/features/messaging/state/ActiveIframeEmbed.ts b/fluxer_app/src/features/messaging/state/ActiveIframeEmbed.ts new file mode 100644 index 000000000..0b689d64c --- /dev/null +++ b/fluxer_app/src/features/messaging/state/ActiveIframeEmbed.ts @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +import {makeAutoObservable} from 'mobx'; + +class ActiveIframeEmbed { + activeId: string | null = null; + + constructor() { + makeAutoObservable(this, {}, {autoBind: true}); + } + + isActive(id: string): boolean { + return this.activeId === id; + } + + claim(id: string): void { + this.activeId = id; + } + + release(id: string): void { + if (this.activeId === id) { + this.activeId = null; + } + } +} + +export default new ActiveIframeEmbed();