From 8de81319ba0b7836414d6241c7e98f761485adb9 Mon Sep 17 00:00:00 2001 From: Simon Huang Date: Sat, 22 Aug 2020 18:07:48 -0400 Subject: [PATCH] added basic video syncing --- src/components/Chat.css | 1 - src/components/Chat.tsx | 4 +- src/pages/Home.tsx | 2 +- src/pages/Room.tsx | 93 +++++++++++++++++++++++- src/services/{random.ts => utilities.ts} | 17 +++++ 5 files changed, 110 insertions(+), 7 deletions(-) rename src/services/{random.ts => utilities.ts} (60%) diff --git a/src/components/Chat.css b/src/components/Chat.css index b687c7a..f73b792 100644 --- a/src/components/Chat.css +++ b/src/components/Chat.css @@ -45,7 +45,6 @@ ion-textarea { } .my-msg { - text-align: right; background: var(--ion-color-primary); color: #fff; } diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index 2d23dd3..5e5123c 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -68,14 +68,16 @@ const Chat: React.FC = ({ roomId, userId }) => { } }, [prevMessages, newMessages, chats]); - // Send message to database and reset textarea field + // Send message to database const sendMessage = async () => { await db.collection('rooms').doc(roomId).collection('messages').add({ createdAt: timestamp, senderId: userId, content: message, + type: 'user', }); + // Reset textarea field setMessage(''); }; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index f7a7bce..adb8e1e 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router'; import { db, timestamp, auth, rtdb } from '../services/firebase'; import './Home.css'; -import { generateAnonName } from '../services/random'; +import { generateAnonName } from '../services/utilities'; const Home: React.FC = () => { const [loading, setLoading] = useState(true); diff --git a/src/pages/Room.tsx b/src/pages/Room.tsx index e850c06..79c160c 100644 --- a/src/pages/Room.tsx +++ b/src/pages/Room.tsx @@ -1,10 +1,10 @@ import { IonCol, IonContent, IonGrid, IonHeader, IonPage, IonRow, IonTitle, IonToolbar } from '@ionic/react'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import ReactPlayer from 'react-player'; import { RouteComponentProps, useHistory } from 'react-router'; import Chat from '../components/Chat'; -import { auth, db, decrement, increment, rtdb } from '../services/firebase'; -import { generateAnonName } from '../services/random'; +import { auth, db, decrement, increment, rtdb, timestamp } from '../services/firebase'; +import { generateAnonName, secondsToTimestamp, timestampToSeconds } from '../services/utilities'; import './Room.css'; const Room: React.FC> = ({ match }) => { @@ -13,8 +13,11 @@ const Room: React.FC> = ({ match }) => { const [validRoom, setValidRoom] = useState(false); const [userId, setUserId] = useState(''); + const [ownerId, setOwnerId] = useState(''); const [loading, setLoading] = useState(true); const [userCount, setUserCount] = useState(0); + const [playing, setPlaying] = useState(false); + const player = useRef(null); // Verify that the roomId exists in db useEffect(() => { @@ -24,6 +27,7 @@ const Room: React.FC> = ({ match }) => { history.push('/'); } else { setValidRoom(true); + setOwnerId(room.data()?.ownerId); } }; @@ -124,6 +128,77 @@ const Room: React.FC> = ({ match }) => { } }, [userId, validRoom, roomId, loading, userCount]); + // Send video playing message to database when owner plays video + const onPlay = async () => { + if (ownerId === userId) { + const currTime = player?.current?.getCurrentTime(); + if (currTime !== undefined) { + await db + .collection('rooms') + .doc(roomId) + .collection('messages') + .add({ + createdAt: timestamp, + senderId: userId, + content: 'started playing the video from ' + secondsToTimestamp(currTime), + type: 'play', + }); + } + } + }; + + const onPause = async () => { + if (ownerId === userId) { + const currTime = player?.current?.getCurrentTime(); + if (currTime !== undefined) { + await db + .collection('rooms') + .doc(roomId) + .collection('messages') + .add({ + createdAt: timestamp, + senderId: userId, + content: 'paused the video at ' + secondsToTimestamp(currTime), + type: 'pause', + }); + } + } + }; + + // Listen for video interactions + useEffect(() => { + if (!loading) { + const videoUnsubscribe = db + .collection('rooms') + .doc(roomId) + .collection('messages') + .where('type', 'in', ['play', 'pause']) + .onSnapshot((querySnapshot) => { + const changes = querySnapshot.docChanges(); + const change = changes[changes.length - 1]; + if (change?.type === 'added') { + const data = change.doc.data(); + if (userId !== data.senderId) { + // Match video timestamp of received message + const arr = data.content.split(' '); + const timestamp = arr[arr.length - 1]; + player.current?.seekTo(timestampToSeconds(timestamp)); + + if (data.type === 'play') { + setPlaying(true); + } else { + setPlaying(false); + } + } + } + }); + + return () => { + videoUnsubscribe(); + }; + } + }, [loading, roomId, userId]); + return ( @@ -137,7 +212,17 @@ const Room: React.FC> = ({ match }) => { - + diff --git a/src/services/random.ts b/src/services/utilities.ts similarity index 60% rename from src/services/random.ts rename to src/services/utilities.ts index eff034d..5a08b20 100644 --- a/src/services/random.ts +++ b/src/services/utilities.ts @@ -49,3 +49,20 @@ export const generateAnonName = (): string => { const animal: string = animals[Math.floor(Math.random() * 20)]; return adj + ' ' + animal; }; + +export const secondsToTimestamp = (seconds: number): string => { + const timestamp = new Date(seconds * 1000).toISOString().substr(11, 8); + if (timestamp.substr(0, 2) === '00') { + return timestamp.substr(3); + } + return timestamp; +}; + +export const timestampToSeconds = (timestamp: string): number => { + let arr: string[]; + arr = timestamp.split(':'); + if (timestamp.length === 8) { + return +arr[0] * 60 * 60 + +arr[1] * 60 + +arr[2]; + } + return +arr[0] * 60 + +arr[1]; +};