Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 78 additions & 55 deletions src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,83 @@
import React, { useCallback, useRef } from 'react'
import React, {
forwardRef,
useCallback,
useImperativeHandle,
useRef,
} from 'react'
import { WebView } from 'react-native-webview'

import template from './template'
import { LayoutProps, PlayerEvents } from './types'

export const Vimeo: React.FC<LayoutProps> = ({
handlers: handlersArr,
videoId,
params,
reference,
language,
...otherProps
}) => {
const webRef = useRef<WebView>()
const url: string = params
? `https://player.vimeo.com/video/${videoId}?${params}`
: `https://player.vimeo.com/video/${videoId}`

const autoPlay = params?.includes('autoplay=1')

const handlers: any = {}

const registerHandlers = useCallback(() => {
PlayerEvents.forEach((name) => {
if (handlersArr) handlers[name] = handlersArr[name]
})
}, [handlers, handlersArr])

registerHandlers()

const onBridgeMessage = useCallback(
(event: any) => {
const payload: { name: string; data: any } = JSON.parse(
event.nativeEvent.data
)
import { LayoutProps, PlayerEvents, VimeoRef } from './types'

let bridgeMessageHandler = handlers[payload?.name]
if (bridgeMessageHandler) bridgeMessageHandler(payload?.data)
export const Vimeo: React.FC<LayoutProps> = forwardRef<VimeoRef, LayoutProps>(
(
{
handlers: handlersArr,
videoId,
params,
reference,
language,
...otherProps
},
[handlers]
)

return (
<WebView
allowsFullscreenVideo={true}
source={{
uri: url,
headers: { Referer: reference, 'Accept-Language': language },
}}
javaScriptEnabled={true}
ref={webRef as any}
onMessage={onBridgeMessage}
scrollEnabled={false}
injectedJavaScript={template(url)}
mediaPlaybackRequiresUserAction={!autoPlay}
{...otherProps}
/>
)
}
ref,
) => {
const webRef = useRef<any>()
const url: string = params
? `https://player.vimeo.com/video/${videoId}?${params}`
: `https://player.vimeo.com/video/${videoId}`

const autoPlay = params?.includes('autoplay=1')

const handlers: any = {}

const registerHandlers = useCallback(() => {
PlayerEvents.forEach((name) => {
if (handlersArr) handlers[name] = handlersArr[name]
})
}, [handlers, handlersArr])

registerHandlers()

useImperativeHandle(ref, () => ({
play: () => sendCommand('play'),
pause: () => sendCommand('pause'),
mute: () => sendCommand('mute'),
unmute: () => sendCommand('unmute'),
seekTo: (value: Number) => sendCommand('seekTo', value),
}))

const sendCommand = (command: string, value?: any) => {
webRef.current?.injectJavaScript(
`window.postMessage('${JSON.stringify({ command, value })}');`,
)
}

const onBridgeMessage = useCallback(
(event: any) => {
const payload: { name: string; data: any } = JSON.parse(
event.nativeEvent.data,
)
let bridgeMessageHandler = handlers[payload?.name]
if (bridgeMessageHandler) bridgeMessageHandler(payload?.data)
},
[handlers],
)

return (
<WebView
allowsFullscreenVideo={true}
source={{
uri: url,
headers: { Referer: reference, 'Accept-Language': language },
}}
javaScriptEnabled={true}
ref={webRef as any}
onMessage={onBridgeMessage}
scrollEnabled={false}
injectedJavaScript={template(url)}
mediaPlaybackRequiresUserAction={!autoPlay}
{...otherProps}
/>
)
},
)
93 changes: 64 additions & 29 deletions src/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,42 +8,70 @@ const sendEvent = (name, data) => {
window.ReactNativeWebView.postMessage(JSON.stringify({ name, data }));
};

const addListeners = () => {
const tryAttachListeners = () => {
const video = document.querySelector('video');
if(!video) return false;

const controls = document.querySelector('.vp-controls');
let isVisibleControls = ${!url.includes('controls=0')};

window.addEventListener("fullscreenchange", (e) => {
const orientation = getOrientation();
sendEvent('fullscreenchange', { e, orientation });
}, false);

if(video) {
video.addEventListener("timeupdate", (e) => {
const percent = Math.round((e.target.currentTime / e.target.duration)*100).toFixed();
sendEvent('timeupdate', { currentTime: e.target.currentTime, duration: e.target.duration, percent });
});
video.addEventListener('audioprocess', (e) => sendEvent('audioprocess', e));
video.addEventListener('canplay', (e) => sendEvent('canplay', e));
video.addEventListener('canplaythrough', (e) => sendEvent('canplaythrough', e));
video.addEventListener('complete', (e) => sendEvent('complete', e));
video.addEventListener('durationchange', (e) => sendEvent('durationchange', e));
video.addEventListener('emptied', (e) => sendEvent('emptied', e));
video.addEventListener('ended', (e) => sendEvent('ended', e));
video.addEventListener('loadeddata', (e) => sendEvent('loadeddata', e));
video.addEventListener('loadedmetadata', (e) => sendEvent('loadedmetadata', e));
video.addEventListener('pause', (e) => sendEvent('pause', e));
video.addEventListener('play', (e) => sendEvent('play', e));
video.addEventListener('playing', (e) => sendEvent('playing', e));
video.addEventListener('ratechange', (e) => sendEvent('ratechange', e));
video.addEventListener('seeked', (e) => sendEvent('seeked', e));
video.addEventListener('seeking', (e) => sendEvent('seeking', e));
video.addEventListener('stalled', (e) => sendEvent('stalled', e));
video.addEventListener('suspend', (e) => sendEvent('suspend', e));
// video.addEventListener('timeupdate', (e) => sendEvent('timeupdate', e));
video.addEventListener('volumechange', (e) => sendEvent('volumechange', e));
video.addEventListener('waiting', (e) => sendEvent('waiting', e));
}

window.addEventListener("message", (event) => {
try {
const message = JSON.parse(event.data);
if (!message || !message.command || !video) return;

switch (message.command) {
case 'play':
video.play();
break;
case 'pause':
video.pause();
break;
case 'seekTo':
video.currentTime = message.value || 0;
break;
case 'mute':
video.muted = true;
break;
case 'unmute':
video.muted = false;
break;
}
} catch (e) {
sendEvent('error', { error: 'Invalid JSON or unknown command' });
}
});

video.addEventListener("timeupdate", (e) => {
const percent = Math.round((e.target.currentTime / e.target.duration)*100).toFixed();
sendEvent('timeupdate', { currentTime: e.target.currentTime, duration: e.target.duration, percent });
});
video.addEventListener('audioprocess', (e) => sendEvent('audioprocess', e));
video.addEventListener('canplay', (e) => sendEvent('canplay', e));
video.addEventListener('canplaythrough', (e) => sendEvent('canplaythrough', e));
video.addEventListener('complete', (e) => sendEvent('complete', e));
video.addEventListener('durationchange', (e) => sendEvent('durationchange', e));
video.addEventListener('emptied', (e) => sendEvent('emptied', e));
video.addEventListener('ended', (e) => sendEvent('ended', e));
video.addEventListener('loadeddata', (e) => sendEvent('loadeddata', e));
video.addEventListener('loadedmetadata', (e) => sendEvent('loadedmetadata', e));
video.addEventListener('pause', (e) => sendEvent('pause', e));
video.addEventListener('play', (e) => sendEvent('play', e));
video.addEventListener('playing', (e) => sendEvent('playing', e));
video.addEventListener('ratechange', (e) => sendEvent('ratechange', e));
video.addEventListener('seeked', (e) => sendEvent('seeked', e));
video.addEventListener('seeking', (e) => sendEvent('seeking', e));
video.addEventListener('stalled', (e) => sendEvent('stalled', e));
video.addEventListener('suspend', (e) => sendEvent('suspend', e));
//video.addEventListener('timeupdate', (e) => sendEvent('timeupdate', e));
video.addEventListener('volumechange', (e) => sendEvent('volumechange', e));
video.addEventListener('waiting', (e) => sendEvent('waiting', e));

setInterval(()=>{
if(controls) {
const visible = !controls.classList.contains("invisible");
Expand All @@ -53,7 +81,14 @@ const addListeners = () => {
}
}
},300);

sendEvent('initialized', null);
return true;
};

setTimeout(function(){addListeners()}, 1000);
if(!tryAttachListeners()){
const interval = setInterval(() => {
if (tryAttachListeners()) clearInterval(interval);
}, 100);
}
`
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface LayoutProps extends WebViewProps {
}

export const PlayerEvents = [
'initialized',
'controlschange',
'fullscreenchange',
'audioprocess',
Expand All @@ -34,3 +35,11 @@ export const PlayerEvents = [
] as const

export type PlayerEvent = (typeof PlayerEvents)[number]

export type VimeoRef = {
play: () => void
pause: () => void
mute: () => void
unmute: () => void
seekTo: (seconds: number) => void
}