このコミットが含まれているのは:
2026-06-07 02:13:40 +09:00
コミット 6e338c8616
+73 -73
ファイルの表示
@@ -1,11 +1,11 @@
import { forwardRef, import { forwardRef,
useCallback, useCallback,
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useLayoutEffect, useLayoutEffect,
useMemo, useMemo,
useRef, useRef,
useState } from 'react' useState } from 'react'
import type { CSSProperties, ForwardedRef } from 'react' import type { CSSProperties, ForwardedRef } from 'react'
@@ -15,19 +15,19 @@ type NiconicoPlayerMessage =
| { eventName: 'enterProgrammaticFullScreen' } | { eventName: 'enterProgrammaticFullScreen' }
| { eventName: 'exitProgrammaticFullScreen' } | { eventName: 'exitProgrammaticFullScreen' }
| { eventName: 'loadComplete' | { eventName: 'loadComplete'
playerId?: string playerId?: string
data: { videoInfo: NiconicoVideoInfo } } data: { videoInfo: NiconicoVideoInfo } }
| { eventName: 'playerMetadataChange' | { eventName: 'playerMetadataChange'
playerId?: string playerId?: string
data: NiconicoMetadata } data: NiconicoMetadata }
| { eventName: 'playerStatusChange' | 'statusChange' | { eventName: 'playerStatusChange' | 'statusChange'
playerId?: string playerId?: string
data?: unknown } data?: unknown }
| { eventName: 'error' | { eventName: 'error'
playerId?: string playerId?: string
data?: unknown data?: unknown
code?: string code?: string
message?: string } message?: string }
type NiconicoCommand = type NiconicoCommand =
| { eventName: 'play'; sourceConnectorType: 1; playerId: string } | { eventName: 'play'; sourceConnectorType: 1; playerId: string }
@@ -53,13 +53,13 @@ type Props = {
export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle>) => { export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle>) => {
const { id, width, height, style = { }, onLoadComplete, onMetadataChange, onError } = props const { id, width, height, style = { }, onLoadComplete, onMetadataChange, onError } = props
const iframeRef = useRef<HTMLIFrameElement> (null) const iframeRef = useRef<HTMLIFrameElement> (null)
const loadCompleteTimerRef = useRef<ReturnType<typeof setTimeout> | null> (null) const loadCompleteTimerRef = useRef<ReturnType<typeof setTimeout> | null> (null)
const playerId = useMemo ( const playerId = useMemo (
() => `nico-${ id }-${ Math.random ().toString (36).slice (2) }`, () => `nico-${ id }-${ Math.random ().toString (36).slice (2) }`,
[id]) [id])
const [screenWidth, setScreenWidth] = useState<CSSProperties['width']> () const [screenWidth, setScreenWidth] = useState<CSSProperties['width']> ()
const [screenHeight, setScreenHeight] = useState<CSSProperties['height']> () const [screenHeight, setScreenHeight] = useState<CSSProperties['height']> ()
@@ -79,38 +79,38 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
const styleFullScreen: CSSProperties = const styleFullScreen: CSSProperties =
fullScreen fullScreen
? { top: 0, ? { top: 0,
left: landscape ? 0 : '100%', left: landscape ? 0 : '100%',
position: 'fixed', position: 'fixed',
width: screenWidth, width: screenWidth,
height: screenHeight, height: screenHeight,
zIndex: 2_147_483_647, zIndex: 2_147_483_647,
maxWidth: 'none', maxWidth: 'none',
transformOrigin: '0% 0%', transformOrigin: '0% 0%',
transform: landscape ? 'none' : 'rotate(90deg)', transform: landscape ? 'none' : 'rotate(90deg)',
WebkitTransformOrigin: '0% 0%', WebkitTransformOrigin: '0% 0%',
WebkitTransform: landscape ? 'none' : 'rotate(90deg)' } WebkitTransform: landscape ? 'none' : 'rotate(90deg)' }
: { } : { }
const margedStyle: CSSProperties = const margedStyle: CSSProperties =
{ border: 'none', maxWidth: '100%', ...style, ...styleFullScreen } { border: 'none', maxWidth: '100%', ...style, ...styleFullScreen }
const clearLoadCompleteTimer = useCallback (() => { const clearLoadCompleteTimer = useCallback (() => {
if (!(loadCompleteTimerRef.current)) if (!(loadCompleteTimerRef.current))
return return
clearTimeout (loadCompleteTimerRef.current) clearTimeout (loadCompleteTimerRef.current)
loadCompleteTimerRef.current = null loadCompleteTimerRef.current = null
}, []) }, [])
const startLoadCompleteTimer = useCallback (() => { const startLoadCompleteTimer = useCallback (() => {
clearLoadCompleteTimer () clearLoadCompleteTimer ()
loadCompleteTimerRef.current = setTimeout (() => { loadCompleteTimerRef.current = setTimeout (() => {
onError?.({ onError?.({
eventName: 'loadCompleteTimeout', eventName: 'loadCompleteTimeout',
reason: 'niconico video length was not reported by embed', reason: 'niconico video length was not reported by embed',
}) })
}, LOAD_COMPLETE_TIMEOUT_MS) }, LOAD_COMPLETE_TIMEOUT_MS)
}, [clearLoadCompleteTimer, onError]) }, [clearLoadCompleteTimer, onError])
const postToPlayer = useCallback ((message: NiconicoCommand) => { const postToPlayer = useCallback ((message: NiconicoCommand) => {
const win = iframeRef.current?.contentWindow const win = iframeRef.current?.contentWindow
@@ -165,21 +165,21 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
useEffect (() => { useEffect (() => {
const onMessage = (event: MessageEvent<NiconicoPlayerMessage>) => { const onMessage = (event: MessageEvent<NiconicoPlayerMessage>) => {
if (!(iframeRef.current) if (!(iframeRef.current)
|| (event.source !== iframeRef.current.contentWindow) || (event.source !== iframeRef.current.contentWindow)
|| (event.origin !== EMBED_ORIGIN)) || (event.origin !== EMBED_ORIGIN))
return return
const data = event.data const data = event.data
if (!(data) if (!(data)
|| typeof data !== 'object' || typeof data !== 'object'
|| !('eventName' in data)) || !('eventName' in data))
return return
if (('playerId' in data) if (('playerId' in data)
&& data.playerId && data.playerId
&& data.playerId !== playerId) && data.playerId !== playerId)
return return
if (data.eventName === 'enterProgrammaticFullScreen') if (data.eventName === 'enterProgrammaticFullScreen')
{ {
@@ -193,12 +193,12 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
return return
} }
if (data.eventName === 'loadComplete') if (data.eventName === 'loadComplete')
{ {
clearLoadCompleteTimer () clearLoadCompleteTimer ()
onLoadComplete?.(data.data.videoInfo) onLoadComplete?.(data.data.videoInfo)
return return
} }
if (data.eventName === 'playerMetadataChange') if (data.eventName === 'playerMetadataChange')
{ {
@@ -206,20 +206,20 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
return return
} }
if (data.eventName === 'error') if (data.eventName === 'error')
{ {
clearLoadCompleteTimer () clearLoadCompleteTimer ()
console.error ('niconico player error:', data) console.error ('niconico player error:', data)
onError?.(data) onError?.(data)
} }
} }
addEventListener ('message', onMessage) addEventListener ('message', onMessage)
return () => removeEventListener ('message', onMessage) return () => removeEventListener ('message', onMessage)
}, [clearLoadCompleteTimer, onError, onLoadComplete, onMetadataChange, playerId]) }, [clearLoadCompleteTimer, onError, onLoadComplete, onMetadataChange, playerId])
useEffect (() => clearLoadCompleteTimer, [clearLoadCompleteTimer]) useEffect (() => clearLoadCompleteTimer, [clearLoadCompleteTimer])
useLayoutEffect (() => { useLayoutEffect (() => {
if (!(fullScreen)) if (!(fullScreen))
@@ -232,7 +232,7 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
const pollingResize = () => { const pollingResize = () => {
if (ended) if (ended)
return return
const isLandscape = innerWidth >= innerHeight const isLandscape = innerWidth >= innerHeight
const windowWidth = `${ isLandscape ? innerWidth : innerHeight }px` const windowWidth = `${ isLandscape ? innerWidth : innerHeight }px`
@@ -246,9 +246,9 @@ export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle
const startPollingResize = () => { const startPollingResize = () => {
if (requestAnimationFrame) if (requestAnimationFrame)
requestAnimationFrame (pollingResize) requestAnimationFrame (pollingResize)
else else
pollingResize () pollingResize ()
} }
startPollingResize () startPollingResize ()