v0.9
This commit is contained in:
@@ -48,6 +48,7 @@ const createRoom = async (req, res) => {
|
|||||||
.collection('rooms')
|
.collection('rooms')
|
||||||
.add({
|
.add({
|
||||||
name: bodydata.roomName,
|
name: bodydata.roomName,
|
||||||
|
player: bodydata.player,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
})
|
})
|
||||||
@@ -206,6 +207,31 @@ const deleteRoom = async (req, res) => {
|
|||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updatePlayer = async (req, res) => {
|
||||||
|
const { player, roomId, uid } = JSON.parse(req.body);
|
||||||
|
const userExists = await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(roomId)
|
||||||
|
.collection('users')
|
||||||
|
.doc(uid)
|
||||||
|
.get()
|
||||||
|
.then((userSnap) => userSnap.exists);
|
||||||
|
if (userExists) {
|
||||||
|
await admin
|
||||||
|
.firestore()
|
||||||
|
.collection('rooms')
|
||||||
|
.doc(roomId)
|
||||||
|
.update({
|
||||||
|
player,
|
||||||
|
})
|
||||||
|
.catch((err) => res.status(500).send(err));
|
||||||
|
res.status(200).send();
|
||||||
|
} else {
|
||||||
|
res.status(401).send('Not in room to sync');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
app.use(cors({ origin: true }));
|
app.use(cors({ origin: true }));
|
||||||
app.use(authenticator);
|
app.use(authenticator);
|
||||||
@@ -220,5 +246,6 @@ app.post('/join', joinRoom);
|
|||||||
app.post('/message', sendMessage);
|
app.post('/message', sendMessage);
|
||||||
app.delete('/leave/:rid/users/:uid', leaveRoom);
|
app.delete('/leave/:rid/users/:uid', leaveRoom);
|
||||||
app.delete('/delete/:rid', deleteRoom);
|
app.delete('/delete/:rid', deleteRoom);
|
||||||
|
app.post('/updatePlayer', updatePlayer);
|
||||||
|
|
||||||
exports.widgets = functions.https.onRequest(app);
|
exports.widgets = functions.https.onRequest(app);
|
||||||
|
|||||||
+32
-1
@@ -5,6 +5,27 @@ const API_HOST = window.location.href.startsWith('http://localhost')
|
|||||||
? 'http://localhost:5001/watch-party-crx/us-central1/widgets'
|
? 'http://localhost:5001/watch-party-crx/us-central1/widgets'
|
||||||
: 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets';
|
: 'https://us-central1-watch-party-crx.cloudfunctions.net/widgets';
|
||||||
|
|
||||||
|
export const PLAYER_STATUS = {
|
||||||
|
PLAYING: 'PLAYING',
|
||||||
|
PAUSED: 'PAUSED',
|
||||||
|
NOT_PLAYING: 'NOT_PLAYING',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PLAYER_SOURCE = {
|
||||||
|
NETFLIX: 'https://www.netflix.',
|
||||||
|
AMAZON: 'https://www.amazon.',
|
||||||
|
HULU: 'https://www.hulu.',
|
||||||
|
LOCAL: 'http://localhost',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPlayerSource = () => {
|
||||||
|
const HREF = window.location.href;
|
||||||
|
if (HREF.startsWith(PLAYER_SOURCE.NETFLIX)) return PLAYER_SOURCE.NETFLIX;
|
||||||
|
if (HREF.startsWith(PLAYER_SOURCE.HULU)) return PLAYER_SOURCE.HULU;
|
||||||
|
if (HREF.startsWith(PLAYER_SOURCE.AMAZON)) return PLAYER_SOURCE.AMAZON;
|
||||||
|
if (HREF.startsWith(PLAYER_SOURCE.LOCAL)) return PLAYER_SOURCE.LOCAL;
|
||||||
|
};
|
||||||
|
|
||||||
export const isAuthed = () => true && firebase.auth().currentUser;
|
export const isAuthed = () => true && firebase.auth().currentUser;
|
||||||
|
|
||||||
export const doAuth = () => firebase.auth().signInAnonymously();
|
export const doAuth = () => firebase.auth().signInAnonymously();
|
||||||
@@ -42,7 +63,7 @@ export const SendMessageApi = async (data) => {
|
|||||||
if (!isAuthed()) await doAuth();
|
if (!isAuthed()) await doAuth();
|
||||||
return fetcher({
|
return fetcher({
|
||||||
ENDPOINT: 'message',
|
ENDPOINT: 'message',
|
||||||
METHOD: 'post',
|
METHOD: 'POST',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -62,3 +83,13 @@ export const DeleteRoomApi = async (data) => {
|
|||||||
METHOD: 'DELETE',
|
METHOD: 'DELETE',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const UpdatePlayerApi = async (data) => {
|
||||||
|
if (isAuthed()) {
|
||||||
|
return fetcher({
|
||||||
|
ENDPOINT: 'updatePlayer',
|
||||||
|
METHOD: 'POST',
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
+45
-4
@@ -1,7 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import Router from 'route-lite';
|
import Router from 'route-lite';
|
||||||
import { Fab, createMuiTheme, ThemeProvider } from '@material-ui/core';
|
import { Fab, createMuiTheme, ThemeProvider, Snackbar } from '@material-ui/core';
|
||||||
import Create from './Create';
|
import Create from './Create';
|
||||||
|
import Loading from './Loading';
|
||||||
|
import firebase from 'firebase/app';
|
||||||
|
import 'firebase/firestore';
|
||||||
|
import Inactive from './Inactive';
|
||||||
|
|
||||||
const darkTheme = createMuiTheme({
|
const darkTheme = createMuiTheme({
|
||||||
palette: {
|
palette: {
|
||||||
@@ -14,12 +18,31 @@ const darkTheme = createMuiTheme({
|
|||||||
|
|
||||||
const Routes = (props) => (
|
const Routes = (props) => (
|
||||||
<Router>
|
<Router>
|
||||||
<Create />
|
<Create {...props} />
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
const closedNotification = { open: false, message: '' };
|
||||||
|
|
||||||
|
const [status, setStatus] = useState({ active: false, message: '' });
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [notification, setNotification] = useState(closedNotification);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLoading(true);
|
||||||
|
firebase
|
||||||
|
.firestore()
|
||||||
|
.collection('server')
|
||||||
|
.doc('status')
|
||||||
|
.onSnapshot((statusSnap) => {
|
||||||
|
setStatus(statusSnap.data());
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// HANDLE VISIBILITY STYLING
|
||||||
const PartyRoutesWrapper = document.getElementById('party-routes-wrapper');
|
const PartyRoutesWrapper = document.getElementById('party-routes-wrapper');
|
||||||
if (visible) {
|
if (visible) {
|
||||||
document.body.classList.add('visible');
|
document.body.classList.add('visible');
|
||||||
@@ -28,11 +51,29 @@ const App = () => {
|
|||||||
document.body.classList.remove('visible');
|
document.body.classList.remove('visible');
|
||||||
PartyRoutesWrapper && PartyRoutesWrapper.classList.remove('visible');
|
PartyRoutesWrapper && PartyRoutesWrapper.classList.remove('visible');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={darkTheme}>
|
<ThemeProvider theme={darkTheme}>
|
||||||
|
<Snackbar
|
||||||
|
className="party-notification-container"
|
||||||
|
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||||
|
open={notification.open}
|
||||||
|
onClose={() => setNotification(closedNotification)}
|
||||||
|
message={notification.message}
|
||||||
|
resumeHideDuration={500}
|
||||||
|
autoHideDuration={2000}
|
||||||
|
/>
|
||||||
<div className="party-content-wrapper">
|
<div className="party-content-wrapper">
|
||||||
<div id="party-routes-wrapper">
|
<div id="party-routes-wrapper">
|
||||||
<Routes />
|
{loading && <Loading />}
|
||||||
|
{status.active ? (
|
||||||
|
<Routes
|
||||||
|
updateLoading={setLoading}
|
||||||
|
notify={(message) => setNotification({ open: true, message })}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Inactive status={status} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Fab onClick={() => setVisible(!visible)} className="watch-party-fab">
|
<Fab onClick={() => setVisible(!visible)} className="watch-party-fab">
|
||||||
<span />
|
<span />
|
||||||
|
|||||||
+39
-4
@@ -16,9 +16,10 @@ import Sync from '@material-ui/icons/SyncOutlined';
|
|||||||
import Exit from '@material-ui/icons/ExitToAppRounded';
|
import Exit from '@material-ui/icons/ExitToAppRounded';
|
||||||
import Share from '@material-ui/icons/ShareRounded';
|
import Share from '@material-ui/icons/ShareRounded';
|
||||||
import Delete from '@material-ui/icons/Delete';
|
import Delete from '@material-ui/icons/Delete';
|
||||||
import { SendMessageApi, LeaveRoomApi, DeleteRoomApi } from './Api';
|
import { SendMessageApi, LeaveRoomApi, DeleteRoomApi, UpdatePlayerApi } from './Api';
|
||||||
import { goTo } from 'route-lite';
|
import { goTo } from 'route-lite';
|
||||||
import Create from './Create';
|
import Create from './Create';
|
||||||
|
import VideoListener from './VideoListener';
|
||||||
|
|
||||||
export default class Chat extends Component {
|
export default class Chat extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -28,8 +29,15 @@ export default class Chat extends Component {
|
|||||||
messages: [],
|
messages: [],
|
||||||
newMessage: '',
|
newMessage: '',
|
||||||
admin: false,
|
admin: false,
|
||||||
|
player: {},
|
||||||
|
localPlayer: {},
|
||||||
};
|
};
|
||||||
this.sendMessage = this.sendMessage.bind(this);
|
this.sendMessage = this.sendMessage.bind(this);
|
||||||
|
this.endParty = this.endParty.bind(this);
|
||||||
|
this.leaveParty = this.leaveParty.bind(this);
|
||||||
|
this.shareParty = this.shareParty.bind(this);
|
||||||
|
this.videoEventListener = this.videoEventListener.bind(this);
|
||||||
|
this.mVideoListener = new VideoListener(this.videoEventListener);
|
||||||
}
|
}
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
window.addEventListener('keyup', (ev) => {
|
window.addEventListener('keyup', (ev) => {
|
||||||
@@ -49,9 +57,12 @@ export default class Chat extends Component {
|
|||||||
.doc(window.localStorage.getItem('watchPartyRoomId'))
|
.doc(window.localStorage.getItem('watchPartyRoomId'))
|
||||||
.onSnapshot(
|
.onSnapshot(
|
||||||
(snap) => {
|
(snap) => {
|
||||||
|
if (snap.data().player.status !== this.state.localPlayer.status) {
|
||||||
|
this.mVideoListener.doUpdate(snap.data().player);
|
||||||
|
}
|
||||||
this.setState(snap.data());
|
this.setState(snap.data());
|
||||||
},
|
},
|
||||||
(err) => console.error(err)
|
(err) => {}
|
||||||
);
|
);
|
||||||
firebase
|
firebase
|
||||||
.firestore()
|
.firestore()
|
||||||
@@ -75,27 +86,50 @@ export default class Chat extends Component {
|
|||||||
const pcc = document.getElementsByClassName('thread-container')[0];
|
const pcc = document.getElementsByClassName('thread-container')[0];
|
||||||
if (pcc) pcc.scrollTop = pcc.scrollHeight;
|
if (pcc) pcc.scrollTop = pcc.scrollHeight;
|
||||||
});
|
});
|
||||||
|
this.mVideoListener.start();
|
||||||
|
this.props.updateLoading(false);
|
||||||
|
}
|
||||||
|
videoEventListener(video) {
|
||||||
|
const { status, url, time, source } = video;
|
||||||
|
if (this.state.player.status !== status) {
|
||||||
|
UpdatePlayerApi({
|
||||||
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
|
player: {
|
||||||
|
status,
|
||||||
|
url,
|
||||||
|
time,
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ localPlayer: video });
|
||||||
}
|
}
|
||||||
endParty() {
|
endParty() {
|
||||||
|
this.props.notify('Ending party...');
|
||||||
|
this.props.updateLoading(true);
|
||||||
const data = {
|
const data = {
|
||||||
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
};
|
};
|
||||||
DeleteRoomApi(data).then((res) => {
|
DeleteRoomApi(data).then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
this.props.notify('Ended party');
|
||||||
window.localStorage.removeItem('watchPartyRoomId');
|
window.localStorage.removeItem('watchPartyRoomId');
|
||||||
goTo(Create);
|
goTo(Create, this.props);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
leaveParty() {
|
leaveParty() {
|
||||||
|
this.props.notify('Leaving party...');
|
||||||
|
this.props.updateLoading(true);
|
||||||
const data = {
|
const data = {
|
||||||
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
roomId: window.localStorage.getItem('watchPartyRoomId'),
|
||||||
uid: firebase.auth().currentUser.uid,
|
uid: firebase.auth().currentUser.uid,
|
||||||
};
|
};
|
||||||
LeaveRoomApi(data).then((res) => {
|
LeaveRoomApi(data).then((res) => {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
|
this.props.notify('Left party');
|
||||||
window.localStorage.removeItem('watchPartyRoomId');
|
window.localStorage.removeItem('watchPartyRoomId');
|
||||||
goTo(Create);
|
goTo(Create, this.props);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -106,6 +140,7 @@ export default class Chat extends Component {
|
|||||||
el.select();
|
el.select();
|
||||||
document.execCommand('copy');
|
document.execCommand('copy');
|
||||||
document.body.removeChild(el);
|
document.body.removeChild(el);
|
||||||
|
this.props.notify('Copied room code');
|
||||||
}
|
}
|
||||||
sendMessage(message) {
|
sendMessage(message) {
|
||||||
const data = {
|
const data = {
|
||||||
|
|||||||
+72
-19
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
@@ -8,17 +8,27 @@ import {
|
|||||||
Paper,
|
Paper,
|
||||||
} from '@material-ui/core';
|
} from '@material-ui/core';
|
||||||
import Save from '@material-ui/icons/Save';
|
import Save from '@material-ui/icons/Save';
|
||||||
import { JoinRoomApi, CreateRoomApi } from './Api';
|
import { JoinRoomApi, CreateRoomApi, PLAYER_STATUS, getPlayerSource } from './Api';
|
||||||
import { goTo } from 'route-lite';
|
import { goTo } from 'route-lite';
|
||||||
import Chat from './Chat';
|
import Chat from './Chat';
|
||||||
|
|
||||||
const saveName = (name) => window.localStorage.setItem('watchPartyUserName', name);
|
const saveName = (data) => {
|
||||||
|
const { name, notify } = data;
|
||||||
|
window.localStorage.setItem('watchPartyUserName', name);
|
||||||
|
notify('Saved name');
|
||||||
|
};
|
||||||
const getName = () => window.localStorage.getItem('watchPartyUserName');
|
const getName = () => window.localStorage.getItem('watchPartyUserName');
|
||||||
|
|
||||||
const saveRoomId = (rid) => window.localStorage.setItem('watchPartyRoomId', rid);
|
const saveRoomId = (data) => {
|
||||||
|
const { rid, notify } = data;
|
||||||
|
window.localStorage.setItem('watchPartyRoomId', rid);
|
||||||
|
notify('Saved room');
|
||||||
|
};
|
||||||
const getRoomId = () => window.localStorage.getItem('watchPartyRoomId');
|
const getRoomId = () => window.localStorage.getItem('watchPartyRoomId');
|
||||||
|
|
||||||
const joinRoom = async (data) => {
|
const joinRoom = async (data) => {
|
||||||
|
data.props.updateLoading(true);
|
||||||
|
data.props.notify('Joining party...');
|
||||||
const apidata = {
|
const apidata = {
|
||||||
roomId: data.joinName.trim(),
|
roomId: data.joinName.trim(),
|
||||||
userName: data.name.trim(),
|
userName: data.name.trim(),
|
||||||
@@ -26,25 +36,58 @@ const joinRoom = async (data) => {
|
|||||||
const confirmedRoomId = await JoinRoomApi(apidata)
|
const confirmedRoomId = await JoinRoomApi(apidata)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => res.roomId)
|
.then((res) => res.roomId)
|
||||||
.catch((err) => console.error('Join Room API error', err));
|
.catch((err) => {
|
||||||
|
data.props.updateLoading(false);
|
||||||
|
data.props.notify('Something went wrong');
|
||||||
|
console.error('Join Room API error', err);
|
||||||
|
});
|
||||||
if (confirmedRoomId === apidata.roomId) {
|
if (confirmedRoomId === apidata.roomId) {
|
||||||
saveRoomId(confirmedRoomId);
|
saveName({
|
||||||
saveName(apidata.userName);
|
name: apidata.userName,
|
||||||
goTo(Chat);
|
notify: data.props.notify,
|
||||||
|
});
|
||||||
|
saveRoomId({
|
||||||
|
rid: confirmedRoomId,
|
||||||
|
notify: data.props.notify,
|
||||||
|
});
|
||||||
|
data.props.notify('Joined party');
|
||||||
|
goTo(Chat, data.props);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createRoom = async (data) => {
|
const createRoom = async (data) => {
|
||||||
|
data.props.updateLoading(true);
|
||||||
|
data.props.notify('Starting party...');
|
||||||
|
|
||||||
const apidata = {
|
const apidata = {
|
||||||
roomName: data.createName,
|
roomName: data.createName,
|
||||||
userName: data.name,
|
userName: data.name,
|
||||||
|
player: {
|
||||||
|
source: getPlayerSource(),
|
||||||
|
status: PLAYER_STATUS.NOT_PLAYING,
|
||||||
|
time: 0,
|
||||||
|
url: '',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
CreateRoomApi(apidata)
|
CreateRoomApi(apidata)
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
saveRoomId(res.roomId);
|
saveName({
|
||||||
saveName(data.name);
|
name: data.name,
|
||||||
goTo(Chat);
|
notify: data.props.notify,
|
||||||
|
});
|
||||||
|
saveRoomId({
|
||||||
|
rid: res.roomId,
|
||||||
|
notify: data.props.notify,
|
||||||
|
});
|
||||||
|
data.props.notify('Started party');
|
||||||
|
goTo(Chat, data.props);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
data.props.updateLoading(false);
|
||||||
|
data.props.notify('Something went wrong');
|
||||||
|
console.error('Join Room API error', err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,11 +95,21 @@ const Create = (props) => {
|
|||||||
const [name, setName] = useState(getName() || '');
|
const [name, setName] = useState(getName() || '');
|
||||||
const [createName, setCreateName] = useState('');
|
const [createName, setCreateName] = useState('');
|
||||||
const [joinName, setJoinName] = useState(getRoomId() || '');
|
const [joinName, setJoinName] = useState(getRoomId() || '');
|
||||||
if (getName() && getRoomId())
|
const { notify, updateLoading } = props;
|
||||||
joinRoom({
|
|
||||||
joinName: getRoomId(),
|
const joinOnMount = () => {
|
||||||
name: getName(),
|
updateLoading(false);
|
||||||
});
|
// NEEDS FIX - REPETITIVE API CALLS
|
||||||
|
// if (getName() && getRoomId()) {
|
||||||
|
// joinRoom({
|
||||||
|
// joinName,
|
||||||
|
// name,
|
||||||
|
// props,
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
useEffect(joinOnMount, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="party-create-container">
|
<div className="party-create-container">
|
||||||
<Typography className="brand-title" variant="h6" display="block" gutterBottom>
|
<Typography className="brand-title" variant="h6" display="block" gutterBottom>
|
||||||
@@ -73,7 +126,7 @@ const Create = (props) => {
|
|||||||
InputProps={{
|
InputProps={{
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton onClick={() => saveName(name)}>
|
<IconButton onClick={() => saveName({ name, notify })}>
|
||||||
<Save />
|
<Save />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
@@ -92,7 +145,7 @@ const Create = (props) => {
|
|||||||
label="Party Name"
|
label="Party Name"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => createRoom({ name, createName })}
|
onClick={() => createRoom({ name, createName, props })}
|
||||||
disabled={createName === ''}
|
disabled={createName === ''}
|
||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -114,7 +167,7 @@ const Create = (props) => {
|
|||||||
label="Party Name"
|
label="Party Name"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => joinRoom({ joinName, name })}
|
onClick={() => joinRoom({ joinName, name, props })}
|
||||||
disabled={joinName === ''}
|
disabled={joinName === ''}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Typography } from '@material-ui/core';
|
||||||
|
import { Warning } from '@material-ui/icons';
|
||||||
|
|
||||||
|
const Inactive = (props) => (
|
||||||
|
<div id="party-loading" className="inactive">
|
||||||
|
<Warning />
|
||||||
|
<Typography variant="overline">PARTY IS PAUSED FOR A LITTLE CLEANUP</Typography>
|
||||||
|
<Typography variant="caption">{props.status.message}</Typography>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Inactive;
|
||||||
+4
-4
@@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { LoadingOutlined } from '@ant-design/icons';
|
import { CircularProgress } from '@material-ui/core';
|
||||||
|
|
||||||
const Loading = () => (
|
const Loading = () => (
|
||||||
<div id="loading_container">
|
<div id="party-loading">
|
||||||
<LoadingOutlined />
|
<CircularProgress />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
import { PLAYER_STATUS, getPlayerSource, PLAYER_SOURCE } from './Api';
|
||||||
|
|
||||||
|
const templateListener = (video) => console.log(video);
|
||||||
|
|
||||||
|
export default class VideoListener {
|
||||||
|
constructor(listener = templateListener) {
|
||||||
|
this.video = {
|
||||||
|
status: PLAYER_STATUS.NOT_PLAYING,
|
||||||
|
time: 0,
|
||||||
|
url: '',
|
||||||
|
source: '',
|
||||||
|
element: null,
|
||||||
|
};
|
||||||
|
this.listener = listener;
|
||||||
|
this.updateStatus = this.updateStatus.bind(this);
|
||||||
|
this.firstTimeSync = this.firstTimeSync.bind(this);
|
||||||
|
this.doUpdate = this.doUpdate.bind(this);
|
||||||
|
this.start = this.start.bind(this);
|
||||||
|
}
|
||||||
|
updateStatus(newStatus) {
|
||||||
|
this.video = Object.assign({}, this.video, newStatus);
|
||||||
|
this.listener(this.video);
|
||||||
|
}
|
||||||
|
doUpdate(serverPlayer) {
|
||||||
|
if (this.video.element) this.video.element.currentTime = serverPlayer.time;
|
||||||
|
switch (serverPlayer.status) {
|
||||||
|
case PLAYER_STATUS.PAUSED:
|
||||||
|
if (this.video.element) this.video.element.pause();
|
||||||
|
break;
|
||||||
|
case PLAYER_STATUS.NOT_PLAYING:
|
||||||
|
if (this.video.element) this.video.element.pause();
|
||||||
|
break;
|
||||||
|
case PLAYER_STATUS.PLAYING:
|
||||||
|
if (this.video.element) this.video.element.play();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
firstTimeSync(videoElement) {
|
||||||
|
this.updateStatus({
|
||||||
|
element: videoElement,
|
||||||
|
});
|
||||||
|
if (videoElement.paused) {
|
||||||
|
this.updateStatus({
|
||||||
|
time: videoElement.currentTime,
|
||||||
|
status: PLAYER_STATUS.PAUSED,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (videoElement.currentTime > 0 && !videoElement.paused) {
|
||||||
|
this.updateStatus({
|
||||||
|
time: videoElement.currentTime,
|
||||||
|
status: PLAYER_STATUS.PLAYING,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
videoElement.onplaying = (ev) => {
|
||||||
|
this.updateStatus({ status: PLAYER_STATUS.PLAYING, time: ev.timeStamp });
|
||||||
|
if (this.video.url === '') this.updateStatus({ url: window.location.href });
|
||||||
|
};
|
||||||
|
videoElement.onpause = (ev) =>
|
||||||
|
this.updateStatus({ status: PLAYER_STATUS.PAUSED, time: ev.timeStamp });
|
||||||
|
videoElement.onended = (ev) => {
|
||||||
|
this.updateStatus({ status: PLAYER_STATUS.NOT_PLAYING, url: '', time: 0 });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
this.updateStatus({ source: getPlayerSource(), url: window.location.href });
|
||||||
|
switch (this.video.source) {
|
||||||
|
case PLAYER_SOURCE.NETFLIX:
|
||||||
|
if (document.getElementsByClassName('AkiraPlayer')[0]) {
|
||||||
|
const videoElement = document
|
||||||
|
.getElementsByClassName('AkiraPlayer')[0]
|
||||||
|
.getElementsByTagName('video')[0];
|
||||||
|
if (videoElement) {
|
||||||
|
this.firstTimeSync(videoElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.body.addEventListener('DOMNodeInserted', (ev) => {
|
||||||
|
if (ev.target.tagName === 'VIDEO') {
|
||||||
|
document.body.removeEventListener('DOMNodeInserted');
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PLAYER_SOURCE.HULU:
|
||||||
|
const videoElement = document.getElementsByClassName('video-player')[0];
|
||||||
|
if (videoElement) {
|
||||||
|
this.firstTimeSync(videoElement);
|
||||||
|
} else {
|
||||||
|
document.body.addEventListener('DOMNodeInserted', (ev) => {
|
||||||
|
if (ev.target.tagName === 'VIDEO') {
|
||||||
|
document.body.removeEventListener('DOMNodeInserted');
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PLAYER_SOURCE.AMAZON:
|
||||||
|
if (document.getElementsByClassName('webPlayerSDKContainer')[0]) {
|
||||||
|
const videoElement = document
|
||||||
|
.getElementsByClassName('webPlayerSDKContainer')[0]
|
||||||
|
.getElementsByTagName('video')[0];
|
||||||
|
if (videoElement) {
|
||||||
|
this.firstTimeSync(videoElement);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
document.body.addEventListener('DOMNodeInserted', (ev) => {
|
||||||
|
if (ev.target.tagName === 'VIDEO') {
|
||||||
|
document.body.removeEventListener('DOMNodeInserted');
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PLAYER_SOURCE.LOCAL:
|
||||||
|
const localVideoElement = document.getElementsByTagName('video')[0];
|
||||||
|
if (localVideoElement) {
|
||||||
|
this.firstTimeSync(localVideoElement);
|
||||||
|
} else {
|
||||||
|
document.body.addEventListener('DOMNodeInserted', (ev) => {
|
||||||
|
if (ev.target.tagName === 'VIDEO') {
|
||||||
|
document.body.removeEventListener('DOMNodeInserted');
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
const DATA_MODEL = {
|
|
||||||
users: {
|
|
||||||
'<someuid>': {
|
|
||||||
uid: '<someuid>',
|
|
||||||
rooms: ['<someroomid>'],
|
|
||||||
},
|
|
||||||
'<someuid2>': {
|
|
||||||
uid: '<someuid2>',
|
|
||||||
rooms: ['<someroomid>'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rooms: {
|
|
||||||
'<someroomid>': {
|
|
||||||
player: {
|
|
||||||
source: 'hulu|netlix|amazon',
|
|
||||||
state: 'playing|paused',
|
|
||||||
time: '<video_time>',
|
|
||||||
},
|
|
||||||
users: ['<someuid>', '<someuid2>'],
|
|
||||||
messages: {
|
|
||||||
'<m1>': {
|
|
||||||
uid: '<someuid>',
|
|
||||||
name: 'Jane Doe',
|
|
||||||
text: 'Hello!!!!',
|
|
||||||
time: '<timestamp>',
|
|
||||||
},
|
|
||||||
'<m2>': {
|
|
||||||
uid: '<someuid2>',
|
|
||||||
name: 'Long John',
|
|
||||||
text: 'oh hi mark',
|
|
||||||
time: '<timestamp>',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DATA_MODEL;
|
|
||||||
+48
-4
@@ -10,6 +10,9 @@ body.visible .watch-party-fab {
|
|||||||
right: calc(35% - 4rem) !important;
|
right: calc(35% - 4rem) !important;
|
||||||
top: 1.5rem;
|
top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
body.visible .party-notification-container {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
body.netflix.visible .AkiraPlayer, /* NETFLIX */
|
body.netflix.visible .AkiraPlayer, /* NETFLIX */
|
||||||
body.hulu.visible .Player__container.addFocus,/* HULU */
|
body.hulu.visible .Player__container.addFocus,/* HULU */
|
||||||
@@ -26,11 +29,41 @@ body.amazon-prime #party-routes-wrapper {
|
|||||||
width: 35% !important;
|
width: 35% !important;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
/* NETFLIX HEADER FIX */
|
|
||||||
body.netflix .pinning-header-container {
|
|
||||||
right: 35% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@keyframes backgroundpulse {
|
||||||
|
0% {
|
||||||
|
background-color: black;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-color: red;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
background-color: black;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#party-loading {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 9999;
|
||||||
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
|
display: flex;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#party-loading.inactive {
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
animation-duration: 4s;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-name: backgroundpulse;
|
||||||
|
}
|
||||||
#party-routes-wrapper {
|
#party-routes-wrapper {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -48,6 +81,17 @@ body.netflix .pinning-header-container {
|
|||||||
#party-routes-wrapper.visible {
|
#party-routes-wrapper.visible {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
.party-notification-container {
|
||||||
|
display: none !important;
|
||||||
|
bottom: 0.8rem !important;
|
||||||
|
right: 1rem !important;
|
||||||
|
width: calc(35% - 2rem) !important;
|
||||||
|
}
|
||||||
|
.party-notification-container .MuiPaper-root {
|
||||||
|
border-radius: 26px !important;
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
.watch-party-fab {
|
.watch-party-fab {
|
||||||
z-index: 9999;
|
z-index: 9999;
|
||||||
position: fixed !important;
|
position: fixed !important;
|
||||||
|
|||||||
+26
-8
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
|||||||
import firebase from 'firebase/app';
|
import firebase from 'firebase/app';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
import { PLAYER_SOURCE, getPlayerSource } from './Api';
|
||||||
|
|
||||||
const firebaseConfig = {
|
const firebaseConfig = {
|
||||||
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
|
apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU',
|
||||||
@@ -21,15 +22,32 @@ const PartyWrapper = document.createElement('div');
|
|||||||
PartyWrapper.id = 'party_wrapper';
|
PartyWrapper.id = 'party_wrapper';
|
||||||
document.body.appendChild(PartyWrapper);
|
document.body.appendChild(PartyWrapper);
|
||||||
|
|
||||||
const HREF = window.location.href;
|
switch (getPlayerSource()) {
|
||||||
|
case PLAYER_SOURCE.NETFLIX:
|
||||||
|
document.body.classList.add('netflix');
|
||||||
|
break;
|
||||||
|
case PLAYER_SOURCE.HULU:
|
||||||
|
document.body.classList.add('hulu');
|
||||||
|
break;
|
||||||
|
case PLAYER_SOURCE.AMAZON:
|
||||||
|
document.body.classList.add('amazon-prime');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// https://www.netflix. -- netflix
|
// document.body.addEventListener('DOMNodeInserted', (ev) =>
|
||||||
// https://www.hulu. -- hulu
|
// console.log('DOC CHANGE', ev.target.tagName === 'VIDEO')
|
||||||
// https://www.amazon. -- amazon prime
|
// );
|
||||||
|
const VideoPlayer = document.createElement('video');
|
||||||
if (HREF.startsWith('https://www.amazon')) document.body.classList.add('amazon-prime');
|
VideoPlayer.autoplay = true;
|
||||||
if (HREF.startsWith('https://www.hulu')) document.body.classList.add('hulu');
|
VideoPlayer.muted = true;
|
||||||
if (HREF.startsWith('https://www.netflix')) document.body.classList.add('netflix');
|
VideoPlayer.controls = true;
|
||||||
|
VideoPlayer.style.width = '300px';
|
||||||
|
VideoPlayer.src =
|
||||||
|
'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4';
|
||||||
|
VideoPlayer.addEventListener('change', (e) => console.log(e));
|
||||||
|
document.body.appendChild(VideoPlayer);
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
-108
@@ -1,108 +0,0 @@
|
|||||||
body.netflix {
|
|
||||||
display: flex;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
||||||
body.netflix #appMountPoint {
|
|
||||||
z-index: 999;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 100vh;
|
|
||||||
resize: horizontal;
|
|
||||||
width: 1802px;
|
|
||||||
margin: 0px;
|
|
||||||
max-width: 100%;
|
|
||||||
flex-grow: 3;
|
|
||||||
flex-basis: 3;
|
|
||||||
}
|
|
||||||
.netflix-sans-font-loaded,
|
|
||||||
.netflix-sans-font-loaded > div,
|
|
||||||
.netflix-sans-font-loaded > div > div,
|
|
||||||
.nf-kb-nav-wrapper,
|
|
||||||
.sizing-wrapper {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
#appMountPoint .netflix-sans-font-loaded > div > div > .nf-kb-nav-wrapper .sizing-wrapper {
|
|
||||||
position: relative !important;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix {
|
|
||||||
position: relative;
|
|
||||||
z-index: 999;
|
|
||||||
flex-shrink: 3;
|
|
||||||
flex-basis: 1;
|
|
||||||
background-color: #1e2126;
|
|
||||||
width: 25%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix .content__wrapper {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
padding: 20px;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix .party_chat_container {
|
|
||||||
max-height: max-content;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
flex: 1;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix h3 {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix h4 {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-weight: 200;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix h4 button {
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
#party_wrapper.netflix h4 span > svg {
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
}
|
|
||||||
.party_chat_input {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.party_chat_input #chat_input {
|
|
||||||
resize: none;
|
|
||||||
max-height: 100px;
|
|
||||||
border-radius: 13px;
|
|
||||||
}
|
|
||||||
.party_chat_input ::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.party_chat_input #chat_send {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.party_chat_input #chat_send svg {
|
|
||||||
height: 1.8rem;
|
|
||||||
width: 1.8rem;
|
|
||||||
}
|
|
||||||
#loading_container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 1000;
|
|
||||||
background-color: rgba(80, 80, 80, 0.9);
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
#loading_container svg {
|
|
||||||
color: wheat;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
.ant-popover-inner-content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user