mirror of https://github.com/shuang854/Turtle
integrated netflix syncing into app
parent
60aa771572
commit
4e57fa3090
@ -0,0 +1,170 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from 'react';
|
||||||
|
import { arrayUnion, db } from '../../services/firebase';
|
||||||
|
import { SYNC_MARGIN } from '../../services/utilities';
|
||||||
|
|
||||||
|
type SubscriptionFrameProps = {
|
||||||
|
roomId: string;
|
||||||
|
ownerId: string;
|
||||||
|
userId: string;
|
||||||
|
videoUrl: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubscriptionFrame: React.FC<SubscriptionFrameProps> = ({ ownerId, userId, roomId, videoUrl }) => {
|
||||||
|
const frameRef = useRef<HTMLIFrameElement>(null);
|
||||||
|
const [playerReady, setPlayerReady] = useState(false);
|
||||||
|
|
||||||
|
// Listen for events from browser extension (owner only)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ownerId === userId) {
|
||||||
|
const handleMessage = (e: MessageEvent) => {
|
||||||
|
const type = e.data.type;
|
||||||
|
|
||||||
|
// Send play/pause events to Firebase
|
||||||
|
if (type === 'play' || type === 'pause') {
|
||||||
|
db.collection('states')
|
||||||
|
.doc(roomId)
|
||||||
|
.update({
|
||||||
|
isPlaying: type === 'play',
|
||||||
|
time: e.data.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
db.collection('rooms')
|
||||||
|
.doc(roomId)
|
||||||
|
.update({
|
||||||
|
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: e.data.time, type: type }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', handleMessage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [roomId, ownerId, userId]);
|
||||||
|
|
||||||
|
// Request current state of video from browser extension
|
||||||
|
const getCurrentStatus = () => {
|
||||||
|
return new Promise<{ isPlaying: boolean; time: number }>((resolve, reject) => {
|
||||||
|
const channel = new MessageChannel();
|
||||||
|
channel.port1.onmessage = ({ data }) => {
|
||||||
|
channel.port1.close();
|
||||||
|
if (data.error) {
|
||||||
|
reject(data.error);
|
||||||
|
} else {
|
||||||
|
resolve({ isPlaying: data.isPlaying, time: data.time });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
frameRef.current?.contentWindow?.postMessage('fetch current status', '*', [channel.port2]);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subscribe listener for updateState requests from Firebase (owner only)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ownerId === userId) {
|
||||||
|
const roomRef = db.collection('rooms').doc(roomId);
|
||||||
|
const stateRef = db.collection('states').doc(roomId);
|
||||||
|
|
||||||
|
// Add a listener to 'rooms' collection, listening for updateState requests
|
||||||
|
const roomUnsubscribe = roomRef.onSnapshot(async (docSnapshot) => {
|
||||||
|
const requests = docSnapshot.data()?.requests;
|
||||||
|
const req = requests[requests.length - 1];
|
||||||
|
|
||||||
|
if (!!req && req.type === 'updateState' && req.senderId !== userId) {
|
||||||
|
const status = await getCurrentStatus();
|
||||||
|
stateRef.update({
|
||||||
|
time: status.time,
|
||||||
|
isPlaying: status.isPlaying,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
roomUnsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [ownerId, roomId, userId]);
|
||||||
|
|
||||||
|
// Listen for browser extension events (member only)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ownerId !== userId) {
|
||||||
|
const handleMessage = (e: MessageEvent) => {
|
||||||
|
const event = e.data.toString();
|
||||||
|
if (event === 'video ready') {
|
||||||
|
setPlayerReady(true);
|
||||||
|
}
|
||||||
|
if (event === 'video not ready') {
|
||||||
|
setPlayerReady(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for current video state updates from browser extension
|
||||||
|
window.addEventListener('message', handleMessage);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', handleMessage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [ownerId, userId, videoUrl]);
|
||||||
|
|
||||||
|
// Listen for changes in video state from Firebase (member only)
|
||||||
|
useEffect(() => {
|
||||||
|
if (ownerId !== userId && playerReady) {
|
||||||
|
const stateRef = db.collection('states').doc(roomId);
|
||||||
|
const roomRef = db.collection('rooms').doc(roomId);
|
||||||
|
let allowUpdate = true;
|
||||||
|
|
||||||
|
// Send player seeking event to browser extension
|
||||||
|
const seekTo = (time: number) => {
|
||||||
|
frameRef.current?.contentWindow?.postMessage({ type: 'seek', currentTime: time }, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Send player set event to browser extension
|
||||||
|
const setPlaying = (isPlaying: boolean) => {
|
||||||
|
frameRef.current?.contentWindow?.postMessage({ type: 'playing', playing: isPlaying }, '*');
|
||||||
|
};
|
||||||
|
|
||||||
|
// listen to 'states' collection for video state changes from owner
|
||||||
|
const stateUnsubscribe = stateRef.onSnapshot(async (docSnapshot) => {
|
||||||
|
const actual = docSnapshot.data();
|
||||||
|
if (allowUpdate) {
|
||||||
|
const status = await getCurrentStatus();
|
||||||
|
console.log('status:', status);
|
||||||
|
setPlaying(actual?.isPlaying);
|
||||||
|
|
||||||
|
if (Math.abs(status.time - actual?.time) > SYNC_MARGIN / 1000 && actual?.isPlaying) {
|
||||||
|
allowUpdate = false;
|
||||||
|
setTimeout(() => {
|
||||||
|
// Throttle update requests
|
||||||
|
allowUpdate = true;
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
seekTo(actual?.time);
|
||||||
|
roomRef.update({
|
||||||
|
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stateUnsubscribe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}, [playerReady, roomId, ownerId, userId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={frameRef}
|
||||||
|
src={videoUrl}
|
||||||
|
frameBorder="0"
|
||||||
|
title="Subscription Service"
|
||||||
|
allow="encrypted-media; fullscreen"
|
||||||
|
allow-scripts=""
|
||||||
|
sandbox="allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-presentation allow-scripts allow-same-origin allow-storage-access-by-user-activation"
|
||||||
|
height="100%"
|
||||||
|
width="100%"
|
||||||
|
></iframe>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SubscriptionFrame;
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { db } from '../../services/firebase';
|
||||||
|
import { matchUrl } from '../../services/utilities';
|
||||||
|
import ReactPlayerFrame from './ReactPlayerFrame';
|
||||||
|
import SubscriptionFrame from './SubscriptionFrame';
|
||||||
|
|
||||||
|
type VideoPlayerProps = {
|
||||||
|
ownerId: string;
|
||||||
|
userId: string;
|
||||||
|
roomId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const VideoPlayer: React.FC<VideoPlayerProps> = ({ roomId, ownerId, userId }) => {
|
||||||
|
const [videoUrl, setVideoUrl] = useState('');
|
||||||
|
|
||||||
|
// Listen for video URL changes
|
||||||
|
useEffect(() => {
|
||||||
|
const playlistRef = db.collection('playlists').doc(roomId);
|
||||||
|
const playlistUnsubscribe = playlistRef.onSnapshot((docSnapshot) => {
|
||||||
|
const data = docSnapshot.data();
|
||||||
|
if (data !== undefined) {
|
||||||
|
let url: string = data.url;
|
||||||
|
if (matchUrl(url) === 'NETFLIX') {
|
||||||
|
// Remove query from url
|
||||||
|
if (url.indexOf('?') > 0) {
|
||||||
|
url = url.substring(0, url.indexOf('?'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setVideoUrl(url);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
playlistUnsubscribe();
|
||||||
|
};
|
||||||
|
}, [roomId]);
|
||||||
|
|
||||||
|
return matchUrl(videoUrl) === 'NETFLIX' ? (
|
||||||
|
<SubscriptionFrame roomId={roomId} ownerId={ownerId} userId={userId} videoUrl={videoUrl}></SubscriptionFrame>
|
||||||
|
) : (
|
||||||
|
<ReactPlayerFrame roomId={roomId} ownerId={ownerId} userId={userId} videoUrl={videoUrl}></ReactPlayerFrame>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VideoPlayer;
|
||||||
Loading…
Reference in New Issue