上映会のし組み作り(#295) (#296)
#295 #295 #295 #295 #295 #295 #295 Co-authored-by: miteruzo <miteruzo@naver.com> Reviewed-on: #296
This commit was merged in pull request #296.
This commit is contained in:
@@ -1,78 +1,204 @@
|
||||
import { useRef, useLayoutEffect, useEffect, useState } from 'react'
|
||||
type Props = { id: string,
|
||||
width: number,
|
||||
height: number,
|
||||
style?: CSSProperties }
|
||||
import { forwardRef,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState } from 'react'
|
||||
|
||||
import type { CSSProperties, FC } from 'react'
|
||||
import type { CSSProperties, ForwardedRef } from 'react'
|
||||
|
||||
import type { NiconicoMetadata, NiconicoVideoInfo, NiconicoViewerHandle } from '@/types'
|
||||
|
||||
type NiconicoPlayerMessage =
|
||||
| { eventName: 'enterProgrammaticFullScreen' }
|
||||
| { eventName: 'exitProgrammaticFullScreen' }
|
||||
| { eventName: 'loadComplete'; playerId?: string; data: { videoInfo: NiconicoVideoInfo } }
|
||||
| { eventName: 'playerMetadataChange'; playerId?: string; data: NiconicoMetadata }
|
||||
| { eventName: 'playerStatusChange' | 'statusChange'; playerId?: string; data?: unknown }
|
||||
| { eventName: 'error'; playerId?: string; data?: unknown; code?: string; message?: string }
|
||||
|
||||
type NiconicoCommand =
|
||||
| { eventName: 'play'; sourceConnectorType: 1; playerId: string }
|
||||
| { eventName: 'pause'; sourceConnectorType: 1; playerId: string }
|
||||
| { eventName: 'seek'; sourceConnectorType: 1; playerId: string; data: { time: number } }
|
||||
| { eventName: 'mute'; sourceConnectorType: 1; playerId: string; data: { mute: boolean } }
|
||||
| { eventName: 'volumeChange'; sourceConnectorType: 1; playerId: string;
|
||||
data: { volume: number } }
|
||||
| { eventName: 'commentVisibilityChange'; sourceConnectorType: 1; playerId: string;
|
||||
data: { commentVisibility: boolean } }
|
||||
|
||||
const EMBED_ORIGIN = 'https://embed.nicovideo.jp'
|
||||
|
||||
type Props = {
|
||||
id: string
|
||||
width: number
|
||||
height: number
|
||||
style?: CSSProperties
|
||||
onLoadComplete?: (info: NiconicoVideoInfo) => void
|
||||
onMetadataChange?: (meta: NiconicoMetadata) => void }
|
||||
|
||||
|
||||
export default ((props: Props) => {
|
||||
const { id, width, height, style = { } } = props
|
||||
export default forwardRef ((props: Props, ref: ForwardedRef<NiconicoViewerHandle>) => {
|
||||
const { id, width, height, style = { }, onLoadComplete, onMetadataChange } = props
|
||||
|
||||
const iframeRef = useRef<HTMLIFrameElement> (null)
|
||||
const playerId = useMemo (() => `nico-${ id }-${ Math.random ().toString (36).slice (2) }`, [id])
|
||||
|
||||
const [screenWidth, setScreenWidth] = useState<CSSProperties['width']> ()
|
||||
const [screenHeight, setScreenHeight] = useState<CSSProperties['height']> ()
|
||||
const [landscape, setLandscape] = useState<boolean> (false)
|
||||
const [fullScreen, setFullScreen] = useState<boolean> (false)
|
||||
|
||||
const src = `https://embed.nicovideo.jp/watch/${id}?persistence=1&oldScript=1&referer=&from=0&allowProgrammaticFullScreen=1`;
|
||||
const src =
|
||||
`${ EMBED_ORIGIN }/watch/${ id }`
|
||||
+ '?jsapi=1'
|
||||
+ `&playerId=${ encodeURIComponent (playerId) }`
|
||||
+ '&persistence=1'
|
||||
+ '&oldScript=1'
|
||||
+ '&referer='
|
||||
+ '&from=0'
|
||||
+ '&allowProgrammaticFullScreen=1'
|
||||
|
||||
const styleFullScreen: CSSProperties = fullScreen ? {
|
||||
top: 0,
|
||||
left: landscape ? 0 : '100%',
|
||||
position: 'fixed',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
zIndex: 2147483647,
|
||||
maxWidth: 'none',
|
||||
transformOrigin: '0% 0%',
|
||||
transform: landscape ? 'none' : 'rotate(90deg)',
|
||||
WebkitTransformOrigin: '0% 0%',
|
||||
WebkitTransform: landscape ? 'none' : 'rotate(90deg)' } : {};
|
||||
const styleFullScreen: CSSProperties =
|
||||
fullScreen
|
||||
? { top: 0,
|
||||
left: landscape ? 0 : '100%',
|
||||
position: 'fixed',
|
||||
width: screenWidth,
|
||||
height: screenHeight,
|
||||
zIndex: 2_147_483_647,
|
||||
maxWidth: 'none',
|
||||
transformOrigin: '0% 0%',
|
||||
transform: landscape ? 'none' : 'rotate(90deg)',
|
||||
WebkitTransformOrigin: '0% 0%',
|
||||
WebkitTransform: landscape ? 'none' : 'rotate(90deg)' }
|
||||
: { }
|
||||
|
||||
const margedStyle = {
|
||||
border: 'none',
|
||||
maxWidth: '100%',
|
||||
...style,
|
||||
...styleFullScreen }
|
||||
const margedStyle: CSSProperties =
|
||||
{ border: 'none', maxWidth: '100%', ...style, ...styleFullScreen }
|
||||
|
||||
const postToPlayer = useCallback ((message: NiconicoCommand) => {
|
||||
const win = iframeRef.current?.contentWindow
|
||||
if (!(win))
|
||||
return
|
||||
|
||||
win.postMessage (message, EMBED_ORIGIN)
|
||||
}, [])
|
||||
|
||||
const play = useCallback (() => {
|
||||
postToPlayer ({ eventName: 'play', sourceConnectorType: 1, playerId })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const pause = useCallback (() => {
|
||||
postToPlayer ({ eventName: 'pause', sourceConnectorType: 1, playerId })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const seek = useCallback ((time: number) => {
|
||||
postToPlayer ({ eventName: 'seek', sourceConnectorType: 1, playerId, data: { time } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const mute = useCallback (() => {
|
||||
postToPlayer ({ eventName: 'mute', sourceConnectorType: 1, playerId, data: { mute: true } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const unmute = useCallback (() => {
|
||||
postToPlayer ({ eventName: 'mute', sourceConnectorType: 1, playerId, data: { mute: false } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const setVolume = useCallback ((volume: number) => {
|
||||
postToPlayer (
|
||||
{ eventName: 'volumeChange', sourceConnectorType: 1, playerId, data: { volume } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const showComments = useCallback (() => {
|
||||
postToPlayer (
|
||||
{ eventName: 'commentVisibilityChange', sourceConnectorType: 1, playerId,
|
||||
data: { commentVisibility: true } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
const hideComments = useCallback (() => {
|
||||
postToPlayer (
|
||||
{ eventName: 'commentVisibilityChange', sourceConnectorType: 1, playerId,
|
||||
data: { commentVisibility: false } })
|
||||
}, [playerId, postToPlayer])
|
||||
|
||||
useImperativeHandle (
|
||||
ref,
|
||||
() => ({ play, pause, seek, mute, unmute, setVolume, showComments, hideComments }),
|
||||
[play, pause, seek, mute, unmute, setVolume, showComments, hideComments])
|
||||
|
||||
useEffect (() => {
|
||||
const onMessage = (event: MessageEvent<any>) => {
|
||||
const onMessage = (event: MessageEvent<NiconicoPlayerMessage>) => {
|
||||
if (!(iframeRef.current)
|
||||
|| (event.source !== iframeRef.current.contentWindow))
|
||||
return
|
||||
|| (event.source !== iframeRef.current.contentWindow)
|
||||
|| (event.origin !== EMBED_ORIGIN))
|
||||
return
|
||||
|
||||
if (event.data.eventName === 'enterProgrammaticFullScreen')
|
||||
setFullScreen (true)
|
||||
else if (event.data.eventName === 'exitProgrammaticFullScreen')
|
||||
setFullScreen (false)
|
||||
const data = event.data
|
||||
|
||||
if (!(data)
|
||||
|| typeof data !== 'object'
|
||||
|| !('eventName' in data))
|
||||
return
|
||||
|
||||
if (('playerId' in data)
|
||||
&& data.playerId
|
||||
&& data.playerId !== playerId)
|
||||
return
|
||||
|
||||
if (data.eventName === 'enterProgrammaticFullScreen')
|
||||
{
|
||||
setFullScreen (true)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.eventName === 'exitProgrammaticFullScreen')
|
||||
{
|
||||
setFullScreen (false)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.eventName === 'loadComplete')
|
||||
{
|
||||
onLoadComplete?.(data.data.videoInfo)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.eventName === 'playerMetadataChange')
|
||||
{
|
||||
onMetadataChange?.(data.data)
|
||||
return
|
||||
}
|
||||
|
||||
if (data.eventName === 'error')
|
||||
console.error ('niconico player error:', data)
|
||||
}
|
||||
|
||||
addEventListener ('message', onMessage)
|
||||
|
||||
return () => removeEventListener ('message', onMessage)
|
||||
}, [])
|
||||
}, [onLoadComplete, onMetadataChange, playerId])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useLayoutEffect (() => {
|
||||
if (!(fullScreen))
|
||||
return
|
||||
|
||||
const initialScrollX = scrollX
|
||||
const initialScrollY = scrollY
|
||||
let timer: NodeJS.Timeout
|
||||
let timer: ReturnType<typeof setTimeout>
|
||||
let ended = false
|
||||
|
||||
const pollingResize = () => {
|
||||
if (ended)
|
||||
return
|
||||
return
|
||||
|
||||
const landscape = innerWidth >= innerHeight
|
||||
const windowWidth = `${landscape ? innerWidth : innerHeight}px`
|
||||
const windowHeight = `${landscape ? innerHeight : innerWidth}px`
|
||||
const isLandscape = innerWidth >= innerHeight
|
||||
const windowWidth = `${ isLandscape ? innerWidth : innerHeight }px`
|
||||
const windowHeight = `${ isLandscape ? innerHeight : innerWidth }px`
|
||||
|
||||
setLandscape (landscape)
|
||||
setLandscape (isLandscape)
|
||||
setScreenWidth (windowWidth)
|
||||
setScreenHeight (windowHeight)
|
||||
timer = setTimeout (startPollingResize, 200)
|
||||
@@ -80,9 +206,9 @@ export default ((props: Props) => {
|
||||
|
||||
const startPollingResize = () => {
|
||||
if (requestAnimationFrame)
|
||||
requestAnimationFrame (pollingResize)
|
||||
requestAnimationFrame (pollingResize)
|
||||
else
|
||||
pollingResize ()
|
||||
pollingResize ()
|
||||
}
|
||||
|
||||
startPollingResize ()
|
||||
@@ -97,15 +223,17 @@ export default ((props: Props) => {
|
||||
useEffect (() => {
|
||||
if (!(fullScreen))
|
||||
return
|
||||
|
||||
scrollTo (0, 0)
|
||||
}, [screenWidth, screenHeight, fullScreen])
|
||||
|
||||
return (
|
||||
<iframe ref={iframeRef}
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
style={margedStyle}
|
||||
allowFullScreen
|
||||
allow="autoplay"/>)
|
||||
}) satisfies FC<Props>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={src}
|
||||
width={width}
|
||||
height={height}
|
||||
style={margedStyle}
|
||||
allowFullScreen
|
||||
allow="autoplay"/>)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user