import { useCallback, useEffect, useRef, useState } from 'react' import YoutubeEmbed from 'react-youtube' import NicoViewer from '@/components/NicoViewer' import TwitterEmbed from '@/components/TwitterEmbed' import { useDialogue } from '@/components/dialogues/DialogueProvider' import { Button } from '@/components/ui/button' import { useClientBehaviourSettings } from '@/lib/useClientBehaviourSettings' import type { FC, RefObject } from 'react' import type { NiconicoMetadata, NiconicoVideoInfo, NiconicoViewerHandle, Post } from '@/types' import type { YouTubePlayer } from 'react-youtube' type YouTubeEvent = { data: T target: YouTubePlayer } type Props = { ref?: RefObject post: Post onLoadComplete?: (info: NiconicoVideoInfo) => void onMetadataChange?: (meta: NiconicoMetadata) => void onVideoReady?: (durationMs: number) => void onPlaybackChange?: (currentTimeMs: number) => number | void onError?: (data: unknown) => void } const PostEmbed: FC = ( { ref, post, onLoadComplete, onMetadataChange, onVideoReady, onPlaybackChange, onError }, ) => { const dialogue = useDialogue () const behaviourSettings = useClientBehaviourSettings () const embedAutoLoadMode = behaviourSettings.embedAutoLoad ?? 'auto' const [manualLoadRequested, setManualLoadRequested] = useState (false) const [framed, setFramed] = useState (false) const [youtubePlayer, setYoutubePlayer] = useState (null) const niconicoVideoReadyRef = useRef (false) const notifyNiconicoVideoReady = useCallback ((durationMs: number) => { if (niconicoVideoReadyRef.current || !(Number.isFinite (durationMs)) || durationMs <= 0) return niconicoVideoReadyRef.current = true onVideoReady?.(durationMs) }, [onVideoReady]) const reportYoutubePlayback = useCallback (async (player: YouTubePlayer) => { const currentTime = await player.getCurrentTime () const currentTimeMs = currentTime * 1_000 const targetTimeMs = onPlaybackChange?.(currentTimeMs) if (typeof targetTimeMs !== 'number') return if (Math.abs (currentTimeMs - targetTimeMs) > 5_000) await player.seekTo (targetTimeMs / 1_000, true) }, [onPlaybackChange]) const handleYoutubeReady = async (event: YouTubeEvent) => { setYoutubePlayer (event.target) try { await event.target.playVideo () const duration = await event.target.getDuration () const durationMs = duration * 1_000 onVideoReady?.(durationMs) if (!(Number.isFinite (durationMs)) || durationMs <= 0) return await reportYoutubePlayback (event.target) } catch (error) { onError?.({ platform: 'youtube', error }) } } const handleYoutubeStateChange = (event: YouTubeEvent) => { void reportYoutubePlayback (event.target) } const handleYoutubeError = (event: YouTubeEvent) => { onError?.({ platform: 'youtube', code: event.data }) } const handleNiconicoLoadComplete = (info: NiconicoVideoInfo) => { notifyNiconicoVideoReady (info.lengthInSeconds * 1_000) onLoadComplete?.(info) } const handleNiconicoMetadataChange = (meta: NiconicoMetadata) => { notifyNiconicoVideoReady (meta.duration) onPlaybackChange?.(meta.currentTime) onMetadataChange?.(meta) } useEffect (() => { niconicoVideoReadyRef.current = false }, [post.url]) useEffect (() => { setManualLoadRequested (false) }, [embedAutoLoadMode, post.url]) useEffect (() => { if (!(youtubePlayer) || !(onPlaybackChange)) return const timer = setInterval ( () => void reportYoutubePlayback (youtubePlayer), 1_000) return () => clearInterval (timer) }, [onPlaybackChange, reportYoutubePlayback, youtubePlayer]) const url = new URL (post.url) const shouldLoadEmbed = embedAutoLoadMode === 'auto' || ( embedAutoLoadMode === 'manual' && manualLoadRequested ) const externalLink = ( 外部ページを開く ) const manualLoadControl = (
{externalLink}
) switch (url.hostname.split ('.').slice (-2).join ('.')) { case 'nicovideo.jp': { const mVideoId = url.pathname.match (/(?<=\/watch\/)[a-zA-Z0-9]+?(?=\/|$)/) if (!(mVideoId)) break const [videoId] = mVideoId if (embedAutoLoadMode === 'off') return externalLink if (shouldLoadEmbed === false) return manualLoadControl return ( ) } case 'twitter.com': case 'x.com': { const mUserId = url.pathname.match (/(?<=\/)[^/]+?(?=\/|$|\?)/) const mStatusId = url.pathname.match (/(?<=\/status\/)\d+?(?=\/|$|\?)/) if (!(mUserId) || !(mStatusId)) break const [userId] = mUserId const [statusId] = mStatusId if (embedAutoLoadMode === 'off') return externalLink if (shouldLoadEmbed === false) return manualLoadControl return } case 'youtube.com': { const videoId = url.searchParams.get ('v') if (!(videoId)) break if (embedAutoLoadMode === 'off') return externalLink if (shouldLoadEmbed === false) return manualLoadControl return ( ) } } return ( <> {framed ? (