diff --git a/functions/index.js b/functions/index.js index 267e026..93e4c7c 100644 --- a/functions/index.js +++ b/functions/index.js @@ -5,11 +5,11 @@ const express = require('express'); const cors = require('cors'); const authenticator = (req, res, next) => { + res.set('Access-Control-Allow-Origin', ['*']); if (req.method === 'OPTIONS') { res.status(200).send(); return next(); } - const token = req.headers.authorization.split(' ')[1]; const reqToken = token.split(':')[0]; const reqUid = token.split(':')[1]; @@ -97,8 +97,8 @@ const joinRoom = async (req, res) => { .doc(bodydata.uid) .get() .then((userSnap) => userSnap.exists); - if (userExists) return res.status(200).send({ roomId: bodydata.roomId }); - if (exists && bodydata.roomId && bodydata.userName) { + if (userExists) res.status(200).send({ roomId: bodydata.roomId }); + else if (!userExists && exists && bodydata.roomId && bodydata.userName) { await admin .firestore() .collection('rooms') @@ -124,9 +124,9 @@ const joinRoom = async (req, res) => { updatedAt: new Date().toISOString(), }); res.status(200).send({ roomId: bodydata.roomId }); - } - if (!bodydata.userName) res.status(400).send('Missing user name'); - else res.status(404).send('Party Not Found'); + } else if (!userExists && !bodydata.userName) + res.status(400).send({ message: 'Missing user name' }); + else res.status(404).send({ message: 'Party Not Found' }); }; const sendMessage = async (req, res) => { @@ -143,9 +143,10 @@ const sendMessage = async (req, res) => { uid, }) .catch((messageErr) => res.status(500).send(messageErr)); - await admin.firestore().collection('rooms').doc(bodydata.roomId).update({ + await admin.firestore().collection('rooms').doc(roomId).update({ updatedAt: new Date().toISOString(), }); + res.status(200).send({ message: 'Sent a message' }); }; const leaveRoom = async (req, res) => { @@ -158,7 +159,7 @@ const leaveRoom = async (req, res) => { .doc(req.params.uid) .delete() .catch((err) => res.status(500).send(err)); - res.status(200).send(); + res.status(200).send({ message: 'Left the party' }); }; const deleteRoom = async (req, res) => { @@ -179,7 +180,7 @@ const deleteRoom = async (req, res) => { }); if (!isAdmin) { console.log('[UNAUTHORIZED] Requested Delete - Room - ', req.params.rid); - res.status(401).send(); + res.status(401).send({ message: 'Not authorized to end this party.' }); return; } await admin @@ -204,11 +205,30 @@ const deleteRoom = async (req, res) => { .doc(req.params.rid) .delete() .catch((err) => res.status(500).send(err)); - res.status(200).send(); + res.status(200).send({ message: 'Ended the party' }); }; const updatePlayer = async (req, res) => { - const { player, roomId, uid } = JSON.parse(req.body); + const { player, roomId, uid, showChanged, userName } = JSON.parse(req.body); + const statusString = + player.status === 'PLAYING' + ? 'started playing from' + : player.status === 'PAUSED' + ? 'paused at' + : player.status === 'NOT_PLAYING' + ? 'stopped at' + : 'is at'; + const timeString = + player.time > 3600 + ? new Date(player.time * 1000).toISOString().substr(11, 8) + : new Date(player.time * 1000).toISOString().substr(14, 5); + const [roomExists, roomData] = await admin + .firestore() + .collection('rooms') + .doc(roomId) + .get() + .then((docSnap) => [docSnap.exists, docSnap.data()]); + if (!roomExists) return res.status(404).send({ message: 'Party not found.' }); const userExists = await admin .firestore() .collection('rooms') @@ -218,17 +238,45 @@ const updatePlayer = async (req, res) => { .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(); + if (showChanged === true) { + await admin + .firestore() + .collection('rooms') + .doc(roomId) + .collection('messages') + .add({ + uid: '', + name: '', + text: `${userName} changed show to \n${player.showName}`, + time: new Date().toISOString(), + }); + } + if (roomData.status !== player.status) { + await admin + .firestore() + .collection('rooms') + .doc(roomId) + .update({ + player, + }) + .catch((err) => res.status(500).send(err)); + await admin + .firestore() + .collection('rooms') + .doc(roomId) + .collection('messages') + .add({ + uid: '', + name: '', + text: `${userName} ${statusString} ${timeString}`, + time: new Date().toISOString(), + }); + return res.status(200).send({ message: 'Synced party player' }); + } else { + return res.status(208).send({ message: 'Player already up to date' }); + } } else { - res.status(401).send('Not in room to sync'); + return res.status(401).send('Not in room to sync'); } }; diff --git a/functions/package.json b/functions/package.json index 8fb4cbf..d4a2ef1 100644 --- a/functions/package.json +++ b/functions/package.json @@ -1,6 +1,7 @@ { - "name": "functions", - "description": "Cloud Functions for Firebase", + "name": "watchparty", + "version": "1.0.0-beta", + "description": "Cloud Functions for Watch Party", "scripts": { "lint": "eslint .", "serve": "firebase emulators:start --only functions", diff --git a/public/background.js b/public/background.js index d7633c9..ec30f46 100644 --- a/public/background.js +++ b/public/background.js @@ -1,8 +1,117 @@ -// Called when the user clicks on the browser action -chrome.browserAction.onClicked.addListener(function(tab) { - // Send a message to the active tab - chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) { - var activeTab = tabs[0]; - chrome.tabs.sendMessage(activeTab.id, { message: 'clicked_browser_action' }); +/* global chrome */ +/*global firebase*/ + +const firebaseConfig = { + apiKey: 'AIzaSyCJLuYpzXLDRmu2ttzFuSGmYcpFxqToWFU', + authDomain: 'watch-party-crx.firebaseapp.com', + databaseURL: 'https://watch-party-crx.firebaseio.com', + projectId: 'watch-party-crx', + storageBucket: 'watch-party-crx.appspot.com', + messagingSenderId: '712242707190', + appId: '1:712242707190:web:65fa7f772e30da364b0ad7', + measurementId: 'G-EZS4PEVG4T', +}; + +firebase.initializeApp(firebaseConfig); +firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL); + +const isAuthed = () => true && firebase.auth().currentUser; +const doAuth = () => firebase.auth().signInAnonymously(); + +const fetcher = async (request, sendResponse) => { + if (!isAuthed()) await doAuth(); + const { url, method } = request; + delete request.type; + delete request.url; + delete request.method; + const idToken = await firebase.auth().currentUser.getIdToken(); + let resStatus = {}; + fetch(url, { + method, + headers: { + Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`, + 'Access-Control-Allow-Origin': '*', + }, + ...request, + }) + .then((res) => { + resStatus = { + status: res.status, + statusText: res.statusText, + }; + return res.json(); + }) + .then((res) => sendResponse({ ...res, ...resStatus })) + .catch((err) => sendResponse({ ...err, status: 503, statusText: "Couldn't talk to server" })); +}; + +const sendAuth = async (sendResponse) => { + if (!isAuthed()) await doAuth(); + sendResponse(firebase.auth().currentUser); +}; + +chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { + const { type } = request; + switch (type) { + case 'API_CALL': + fetcher(request, sendResponse); + break; + case 'GET_AUTH': + sendAuth(sendResponse); + break; + default: + console.warn('Unknown message type: <', type, '>'); + break; + } + return true; +}); + +chrome.runtime.onConnect.addListener((port) => { + port.onDisconnect.addListener(() => {}); + port.onMessage.addListener((request) => { + const { type, roomId } = request; + if (type === 'INIT') { + firebase + .firestore() + .collection('rooms') + .doc(roomId) + .onSnapshot( + (snap) => { + if (snap) { + port.postMessage(snap.data()); + } + }, + (err) => {} + ); + firebase + .firestore() + .collection('rooms') + .doc(roomId) + .collection('users') + .onSnapshot( + (snap) => { + if (snap) { + const isAdmin = snap.docs + .filter((user) => user.id === firebase.auth().currentUser.uid)[0] + .data().admin; + port.postMessage({ admin: isAdmin }); + } + }, + (err) => {} + ); + firebase + .firestore() + .collection('rooms') + .doc(roomId) + .collection('messages') + .orderBy('time') + .onSnapshot( + (snap) => { + if (snap) + port.postMessage({ messages: snap.docs.map((v) => ({ ...v.data(), id: v.id })) }); + }, + (err) => {} + ); + } }); }); diff --git a/public/manifest.json b/public/manifest.json index 157ea8b..1fcc7c6 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,15 +1,13 @@ { "name": "WatchParty", "short_name": "WP", - "version": "1.0", + "version": "1.0.0", "description": "Watch streamed content together.", "background": { - "scripts": ["background.js"] - }, - "browser_action": { - "default_popup": "popup.html" + "page": "popup.html" }, "permissions": ["https://*.hulu.com/*", "https://*.netflix.com/*", "https://*.amazon.com/*"], + "content_security_policy": "script-src 'self' https://www.gstatic.com/ https://*.firebaseio.com https://www.googleapis.com; object-src 'self'", "content_scripts": [ { "matches": ["https://*.hulu.com/*"], diff --git a/public/popup.html b/public/popup.html index 9c499dd..ddcc5ad 100644 --- a/public/popup.html +++ b/public/popup.html @@ -1,6 +1,10 @@ + + + + + Hulu Netflix Prime Video diff --git a/src/Api.js b/src/Api.js index f583b2d..9eecb0a 100644 --- a/src/Api.js +++ b/src/Api.js @@ -1,5 +1,4 @@ -import firebase from 'firebase/app'; -import 'firebase/auth'; +/*global chrome*/ const API_HOST = window.location.href.startsWith('http://localhost') ? 'http://localhost:5001/watch-party-crx/us-central1/widgets' @@ -26,70 +25,80 @@ export const getPlayerSource = () => { if (HREF.startsWith(PLAYER_SOURCE.LOCAL)) return PLAYER_SOURCE.LOCAL; }; -export const isAuthed = () => true && firebase.auth().currentUser; - -export const doAuth = () => firebase.auth().signInAnonymously(); - -export const fetcher = async ({ ENDPOINT, METHOD, ...args }) => { - const idToken = await firebase.auth().currentUser.getIdToken(); - return fetch(`${API_HOST}/${ENDPOINT}`, { - method: METHOD, - headers: { - Authorization: `Token ${idToken}:${firebase.auth().currentUser.uid}`, - }, - ...args, +export const getUserUid = async () => + new Promise((resolve, reject) => { + chrome.runtime.sendMessage( + { + type: 'GET_AUTH', + }, + (response) => { + const { uid } = response; + window.localStorage.setItem('watchPartyUserUid', uid); + resolve(uid); + } + ); }); + +export const fetcher = async ({ endpoint, method, ...args }) => { + const url = `${API_HOST}/${endpoint}`; + return new Promise((resolve, reject) => + chrome.runtime.sendMessage( + { + type: 'API_CALL', + url, + method, + ...args, + }, + (response) => { + if (response.status > 299) reject(response); + else resolve({ json: () => response }); + } + ) + ); }; export const CreateRoomApi = async (data) => { - if (!isAuthed()) await doAuth(); return fetcher({ - ENDPOINT: 'create', - METHOD: 'POST', + endpoint: 'create', + method: 'POST', body: JSON.stringify(data), }); }; export const JoinRoomApi = async (data) => { - if (!isAuthed()) await doAuth(); return fetcher({ - ENDPOINT: 'join', - METHOD: 'POST', + endpoint: 'join', + method: 'POST', body: JSON.stringify(data), }); }; export const SendMessageApi = async (data) => { - if (!isAuthed()) await doAuth(); return fetcher({ - ENDPOINT: 'message', - METHOD: 'POST', + endpoint: 'message', + method: 'POST', body: JSON.stringify(data), }); }; export const LeaveRoomApi = async (data) => { - if (!isAuthed()) await doAuth(); return fetcher({ - ENDPOINT: `leave/${data.roomId}/users/${data.uid}`, - METHOD: 'DELETE', + endpoint: `leave/${data.roomId}/users/${data.uid}`, + method: 'DELETE', }); }; export const DeleteRoomApi = async (data) => { - if (!isAuthed()) await doAuth(); return fetcher({ - ENDPOINT: `delete/${data.roomId}`, - METHOD: 'DELETE', + endpoint: `delete/${data.roomId}`, + method: 'DELETE', }); }; export const UpdatePlayerApi = async (data) => { - if (isAuthed()) { - return fetcher({ - ENDPOINT: 'updatePlayer', - METHOD: 'POST', - body: JSON.stringify(data), - }); - } + return fetcher({ + endpoint: 'updatePlayer', + method: 'POST', + body: JSON.stringify(data), + }); }; diff --git a/src/App.js b/src/App.js index 8ca7172..d22f32c 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ import Loading from './Loading'; import firebase from 'firebase/app'; import 'firebase/firestore'; import Inactive from './Inactive'; +import { getUserUid } from './Api'; const darkTheme = createMuiTheme({ palette: { @@ -32,6 +33,7 @@ const App = () => { useEffect(() => { setLoading(true); + getUserUid(); firebase .firestore() .collection('server') diff --git a/src/Chat.js b/src/Chat.js index 6e102d0..6801bc5 100644 --- a/src/Chat.js +++ b/src/Chat.js @@ -1,3 +1,4 @@ +/*global chrome */ import React, { Component } from 'react'; import { Typography, @@ -7,16 +8,19 @@ import { ButtonGroup, Button, } from '@material-ui/core'; -import firebase from 'firebase/app'; -import 'firebase/firestore'; -import 'firebase/auth'; import Thread from './Thread'; import Send from '@material-ui/icons/SendRounded'; -import Sync from '@material-ui/icons/SyncOutlined'; import Exit from '@material-ui/icons/ExitToAppRounded'; import Share from '@material-ui/icons/ShareRounded'; import Delete from '@material-ui/icons/Delete'; -import { SendMessageApi, LeaveRoomApi, DeleteRoomApi, UpdatePlayerApi } from './Api'; +import { + SendMessageApi, + LeaveRoomApi, + DeleteRoomApi, + UpdatePlayerApi, + getPlayerSource, + PLAYER_STATUS, +} from './Api'; import { goTo } from 'route-lite'; import Create from './Create'; import VideoListener from './VideoListener'; @@ -28,7 +32,7 @@ export default class Chat extends Component { name: '', messages: [], newMessage: '', - admin: false, + admin: null, player: {}, localPlayer: {}, }; @@ -36,73 +40,74 @@ export default class Chat extends Component { this.endParty = this.endParty.bind(this); this.leaveParty = this.leaveParty.bind(this); this.shareParty = this.shareParty.bind(this); + this.syncPlayer = this.syncPlayer.bind(this); this.videoEventListener = this.videoEventListener.bind(this); this.mVideoListener = new VideoListener(this.videoEventListener); + this.port = null; } componentDidMount() { - window.addEventListener('keyup', (ev) => { + window.onkeypress = (ev) => { if (ev.code === 'Enter') { if (ev.target === document.getElementById('chat-message-box-input')) { if (ev.shiftKey === false) { + ev.preventDefault(); const trim = this.state.newMessage.replace(/^\s+|\s+$/g, ''); if (trim !== '') this.sendMessage(trim); this.setState({ newMessage: '' }); } } } + }; + this.port = chrome.runtime.connect({ name: window.localStorage.getItem('watchPartyRoomId') }); + this.port.postMessage({ + type: 'INIT', + roomId: window.localStorage.getItem('watchPartyRoomId'), + }); + this.port.onMessage.addListener((response) => { + this.setState(response); + if (response.player && this.state.localPlayer.status !== response.player.status) { + if (response.player.source !== getPlayerSource()) { + this.props.updateLoading(true); + this.props.notify('This party is for a different website'); + goTo(Create, this.props); + this.props.updateLoading(false); + } else { + if (this.mVideoListener.isUpdating === false) + this.mVideoListener.doUpdate(response.player); + } + } + const pcc = document.getElementsByClassName('thread-container')[0]; + if (pcc) pcc.scrollTop = pcc.scrollHeight; }); - firebase - .firestore() - .collection('rooms') - .doc(window.localStorage.getItem('watchPartyRoomId')) - .onSnapshot( - (snap) => { - if (snap.data().player.status !== this.state.localPlayer.status) { - this.mVideoListener.doUpdate(snap.data().player); - } - this.setState(snap.data()); - }, - (err) => {} - ); - firebase - .firestore() - .collection('rooms') - .doc(window.localStorage.getItem('watchPartyRoomId')) - .collection('users') - .onSnapshot((snap) => { - const isAdmin = snap.docs - .filter((user) => user.id === firebase.auth().currentUser.uid)[0] - .data().admin; - this.setState({ admin: isAdmin }); - }); - firebase - .firestore() - .collection('rooms') - .doc(window.localStorage.getItem('watchPartyRoomId')) - .collection('messages') - .orderBy('time') - .onSnapshot((snap) => { - this.setState({ messages: snap.docs.map((v) => ({ ...v.data(), id: v.id })) }); - const pcc = document.getElementsByClassName('thread-container')[0]; - 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) { + this.setState({ localPlayer: video }); + if ( + this.state.player.status !== video.status && + video.showName && + video.status !== PLAYER_STATUS.NOT_PLAYING + ) + this.syncPlayer(video); + } + syncPlayer(video) { + const { status, showName, time, source, element } = video; + if (element) { UpdatePlayerApi({ roomId: window.localStorage.getItem('watchPartyRoomId'), + showChanged: showName !== this.state.player.showName, + userName: window.localStorage.getItem('watchPartyUserName'), player: { - status, - url, + status: showName !== this.state.player.showName ? PLAYER_STATUS.PAUSED : status, + showName, time, source, }, }); + } else { + this.mVideoListener.start(); } - this.setState({ localPlayer: video }); } endParty() { this.props.notify('Ending party...'); @@ -111,11 +116,10 @@ export default class Chat extends Component { roomId: window.localStorage.getItem('watchPartyRoomId'), }; DeleteRoomApi(data).then((res) => { - if (res.status === 200) { - this.props.notify('Ended party'); - window.localStorage.removeItem('watchPartyRoomId'); - goTo(Create, this.props); - } + this.props.notify('Ended party'); + window.localStorage.removeItem('watchPartyRoomId'); + goTo(Create, this.props); + this.props.updateLoading(false); }); } leaveParty() { @@ -123,14 +127,13 @@ export default class Chat extends Component { this.props.updateLoading(true); const data = { roomId: window.localStorage.getItem('watchPartyRoomId'), - uid: firebase.auth().currentUser.uid, + uid: window.localStorage.getItem('watchPartyUserUid'), }; LeaveRoomApi(data).then((res) => { - if (res.status === 200) { - this.props.notify('Left party'); - window.localStorage.removeItem('watchPartyRoomId'); - goTo(Create, this.props); - } + this.props.notify('Left party'); + window.localStorage.removeItem('watchPartyRoomId'); + goTo(Create, this.props); + this.props.updateLoading(false); }); } shareParty() { @@ -146,7 +149,7 @@ export default class Chat extends Component { const data = { roomId: window.localStorage.getItem('watchPartyRoomId'), name: window.localStorage.getItem('watchPartyUserName'), - uid: firebase.auth().currentUser.uid, + uid: window.localStorage.getItem('watchPartyUserUid'), text: message, time: new Date().toISOString(), }; @@ -166,7 +169,6 @@ export default class Chat extends Component { - @@ -180,13 +182,12 @@ export default class Chat extends Component { console.log(e)} multiline rowsMax="3" size="small" placeholder="New message... ( to send)" variant="outlined" - className="chat-message-box" + className="chat-message-box addFocus" value={newMessage} onChange={(e) => this.setState({ newMessage: e.target.value })} InputProps={{ diff --git a/src/Create.js b/src/Create.js index 18b29af..64c2315 100644 --- a/src/Create.js +++ b/src/Create.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Button, InputAdornment, @@ -38,10 +38,10 @@ const joinRoom = async (data) => { .then((res) => res.roomId) .catch((err) => { data.props.updateLoading(false); - data.props.notify('Something went wrong'); + data.props.notify(err.statusText); console.error('Join Room API error', err); }); - if (confirmedRoomId === apidata.roomId) { + if (confirmedRoomId && confirmedRoomId === apidata.roomId) { saveName({ name: apidata.userName, notify: data.props.notify, @@ -73,21 +73,26 @@ const createRoom = async (data) => { CreateRoomApi(apidata) .then((res) => res.json()) .then((res) => { - saveName({ - name: data.name, - notify: data.props.notify, - }); - saveRoomId({ - rid: res.roomId, - notify: data.props.notify, - }); - data.props.notify('Started party'); - goTo(Chat, data.props); + if (res.status <= 299) { + saveName({ + name: data.name, + notify: data.props.notify, + }); + saveRoomId({ + rid: res.roomId, + notify: data.props.notify, + }); + data.props.notify('Started party'); + goTo(Chat, data.props); + } else { + data.props.updateLoading(false); + data.props.notify(res.statusText); + } }) .catch((err) => { data.props.updateLoading(false); - data.props.notify('Something went wrong'); - console.error('Join Room API error', err); + data.props.notify(err.statusText); + console.error('Create Room API error', err); }); }; @@ -95,20 +100,7 @@ const Create = (props) => { const [name, setName] = useState(getName() || ''); const [createName, setCreateName] = useState(''); const [joinName, setJoinName] = useState(getRoomId() || ''); - const { notify, updateLoading } = props; - - const joinOnMount = () => { - updateLoading(false); - // NEEDS FIX - REPETITIVE API CALLS - // if (getName() && getRoomId()) { - // joinRoom({ - // joinName, - // name, - // props, - // }); - // } - }; - useEffect(joinOnMount, []); + const { notify } = props; return (
@@ -140,7 +132,7 @@ const Create = (props) => { setCreateName(e.target.value)} label="Party Name" /> @@ -162,7 +154,7 @@ const Create = (props) => { color="secondary" fullWidth value={joinName} - className="card-field" + className="card-field addFocus" onChange={(e) => setJoinName(e.target.value)} label="Party Name" /> diff --git a/src/Thread.js b/src/Thread.js index 1d873ec..22c8545 100644 --- a/src/Thread.js +++ b/src/Thread.js @@ -1,18 +1,17 @@ import React from 'react'; import { Typography } from '@material-ui/core'; import './Thread.css'; -import firebase from 'firebase/app'; -import 'firebase/auth'; const Thread = (props) => props.messages.map((m, midx) => { + const userUid = window.localStorage.getItem('watchPartyUserUid'); if (m.uid === '') { return ( {m.text} ); - } else if (m.uid === firebase.auth().currentUser.uid) { + } else if (m.uid === userUid) { return (
{ + if (e.innerHTML) nepisode += e.innerHTML + ' '; + }); + return nshow + '\n' + nepisode; + } break; + case PLAYER_SOURCE.AMAZON: + const ashow = document.querySelector('.f3w9jrr.ff48h02').firstChild.innerHTML; + const aepisode = document.querySelector('.f3w9jrr.ff48h02').lastChild.innerHTML; + return ashow !== '' && aepisode !== '' ? ashow + '\n' + aepisode : null; + case PLAYER_SOURCE.HULU: + const hshow = document.querySelector('.metadata-area__second-line').innerHTML; + const hepisode = document.querySelector('.metadata-area__third-line').innerHTML; + return hshow !== '' && hepisode !== '' ? hshow + '\n' + hepisode : null; default: break; } @@ -40,6 +74,7 @@ export default class VideoListener { firstTimeSync(videoElement) { this.updateStatus({ element: videoElement, + showName: this.getShowName(), }); if (videoElement.paused) { this.updateStatus({ @@ -54,17 +89,24 @@ export default class VideoListener { }); } videoElement.onplaying = (ev) => { - this.updateStatus({ status: PLAYER_STATUS.PLAYING, time: ev.timeStamp }); - if (this.video.url === '') this.updateStatus({ url: window.location.href }); + this.updateStatus({ + status: PLAYER_STATUS.PLAYING, + time: videoElement.currentTime, + showName: this.getShowName(), + }); }; videoElement.onpause = (ev) => - this.updateStatus({ status: PLAYER_STATUS.PAUSED, time: ev.timeStamp }); + this.updateStatus({ + status: PLAYER_STATUS.PAUSED, + time: videoElement.currentTime, + showName: this.getShowName(), + }); videoElement.onended = (ev) => { - this.updateStatus({ status: PLAYER_STATUS.NOT_PLAYING, url: '', time: 0 }); + this.updateStatus({ status: PLAYER_STATUS.NOT_PLAYING, showName: '', time: 0 }); }; } start() { - this.updateStatus({ source: getPlayerSource(), url: window.location.href }); + this.updateStatus({ source: getPlayerSource(), showName: this.getShowName() }); switch (this.video.source) { case PLAYER_SOURCE.NETFLIX: if (document.getElementsByClassName('AkiraPlayer')[0]) { @@ -75,25 +117,28 @@ export default class VideoListener { this.firstTimeSync(videoElement); } } else { - document.body.addEventListener('DOMNodeInserted', (ev) => { - if (ev.target.tagName === 'VIDEO') { - document.body.removeEventListener('DOMNodeInserted'); - this.start(); + document.addEventListener('DOMNodeInserted', (e) => { + if (e.target) { + if (e.target.classList) { + if ( + e.target.classList.contains('nfp') && + e.target.classList.contains('AkiraPlayer') + ) { + e.target.addEventListener('DOMNodeInserted', (ev) => { + if (ev.target.children[0].id) { + this.firstTimeSync(ev.target.children[0].firstChild); + } + }); + } + } } }); } break; case PLAYER_SOURCE.HULU: - const videoElement = document.getElementsByClassName('video-player')[0]; + const videoElement = document.getElementsByClassName('content-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: @@ -104,26 +149,12 @@ export default class VideoListener { 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: diff --git a/src/index.css b/src/index.css index 91338f1..ea868bf 100644 --- a/src/index.css +++ b/src/index.css @@ -7,7 +7,8 @@ body.visible { width: 65%; } body.visible .watch-party-fab { - right: calc(35% - 4rem) !important; + right: 35% !important; + transform: translateX(50%); top: 1.5rem; } body.visible .party-notification-container { @@ -15,13 +16,14 @@ body.visible .party-notification-container { } body.netflix.visible .AkiraPlayer, /* NETFLIX */ -body.hulu.visible .Player__container.addFocus,/* HULU */ +body.hulu.visible .Player__container,/* HULU */ body.amazon-prime.visible #dv-web-player.dv-player-fullscreen /* AMAZON PRIME */ { max-width: 65% !important; } /* HULU MINIMIZED VIDEO */ -body.hulu.visible #__player__.Player__container.Player__container--modal { +body.hulu.visible #__player__ .Player__container.Player__container--modal { right: calc(35% + 2rem) !important; + z-index: 99999 !important; } /* HULU & AMAZON PRIME FIX */ body.hulu #party-routes-wrapper, @@ -77,11 +79,13 @@ body.amazon-prime #party-routes-wrapper { color: white; display: flex; flex-direction: column; + z-index: 9999; } #party-routes-wrapper.visible { right: 0; } .party-notification-container { + z-index: 99999 !important; display: none !important; bottom: 0.8rem !important; right: 1rem !important; @@ -97,6 +101,7 @@ body.amazon-prime #party-routes-wrapper { position: fixed !important; bottom: 1rem !important; right: -2rem !important; + border: 1px solid white; background-color: black !important; /* background-image: url('/logo192.png'); */ background-image: url('chrome-extension://__MSG_@@extension_id__/logo192.png'); diff --git a/src/index.js b/src/index.js index 5a58a03..1bba75a 100644 --- a/src/index.js +++ b/src/index.js @@ -27,11 +27,34 @@ switch (getPlayerSource()) { document.body.classList.add('netflix'); break; case PLAYER_SOURCE.HULU: + document.addEventListener( + 'keydown', + (ev) => { + if (ev.target.tagName !== 'INPUT' || ev.target.tagName !== 'TEXTAREA') { + ev.stopImmediatePropagation(); + ev.stopPropagation(); + } else { + ev.target.focus(); + } + }, + { capture: true } + ); document.body.classList.add('hulu'); break; case PLAYER_SOURCE.AMAZON: document.body.classList.add('amazon-prime'); break; + case PLAYER_SOURCE.LOCAL: + const VideoPlayer = document.createElement('video'); + VideoPlayer.autoplay = true; + VideoPlayer.muted = true; + 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); + break; default: break; }