Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] Add Stream Source allocation based on IP region #125

Merged
merged 3 commits into from
Apr 5, 2022
Merged
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
5 changes: 5 additions & 0 deletions app/.env.local.sample
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,8 @@ ROCKETCHAT_URL="base url of rocket chat server"

NEXT_PUBLIC_ROCKET_CHAT_GREENROOM_RTMP="rtmp://bkk.contribute.live-video.net/app/{stream_key}"

# STREAM-SOURCE-Environment

IPINFO_TOKEN = "6ea41123456765"
NEXT_PUBLIC_SERVER_STREAM_LINK0 = "https://{some_url}/hls/stream.m3u8"
NEXT_PUBLIC_SERVER_STREAM_LINK1 = "https://{some_other_url}/hls/stream.m3u8"
77 changes: 48 additions & 29 deletions app/components/clientsideonly/videostreamer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,32 @@ import Head from "next/head";
import { useEffect, useState } from "react";

export default function Videostreamer(props) {
const [ping, setPing] = useState(false)
const [ping, setPing] = useState(false);

const pingStream = async () => {
const response = await fetch(props.src)
const response = await fetch(props.src);
if (response.ok) {
setPing(true)
}
return;
}
useEffect(() => {
setInterval(() => {
pingStream().catch(e => {
console.error("Stream error", e)
setPing(false)
})
}, 60000)

}, [])
return response;
};
useEffect(() => {
setInterval(async () => {
pingStream()
.catch((e) => {
console.error("Stream error", e);
setPing(false);
return
});

}, 30000);
}, []);

const handleToast = () => {
setPing(true)
}
setPing(true);
};

return (
<>
<Head>
Expand All @@ -45,7 +50,7 @@ export default function Videostreamer(props) {
<video
autoPlay
id="my-video"
class="video-js vjs-big-play-centered vjs-responsive"
className="video-js vjs-big-play-centered vjs-responsive"
controls
preload="auto"
poster={props.poster}
Expand All @@ -60,23 +65,37 @@ export default function Videostreamer(props) {
</a>
</p>
</video>
<Alert show={!ping} handleToast={handleToast}/>

<Alert
show={!ping}
handleToast={handleToast}
/>
</Col>
</>
);
}

const Alert = ({handleToast, show}) => {
const Alert = ({ handleToast, show }) => {
return (
<ToastContainer show={show} position="bottom-start" style={{zIndex: "10"}} className="p-3">
<Toast show={show} onClose={handleToast} bg="info">
<Toast.Header>
<strong className="me-auto">Stream Alert!</strong>
<small className="text-muted">just now</small>
</Toast.Header>
<Toast.Body>Thank you for tuning in! Looks like the streaming has stopped! Please stay tune!</Toast.Body>
</Toast>
</ToastContainer>
)
}
<ToastContainer
position="bottom-start"
style={{ zIndex: "10" }}
className="p-3"
>
<Toast
show={show}
onClose={handleToast}
delay={60000}
autohide
bg="warning"
>
<Toast.Header>
<strong className="me-auto">Stream Alert!</strong>
</Toast.Header>
<Toast.Body>
Thank you for watching! Looks like the streaming has stopped! Please
stay tune and refresh the page if this alert does not shows up!
</Toast.Body>
</Toast>
</ToastContainer>
);
};
11 changes: 11 additions & 0 deletions app/lib/geoAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import axios from "axios";
const ipToken = process.env.IPINFO_TOKEN;

export const getIPInfo = async () => {
try {
const res = await axios.get(`https://ipinfo.io/json?token=${ipToken}`);
return res;
} catch (e) {
console.log("error while fetching IPInfo", e);
}
};
65 changes: 48 additions & 17 deletions app/pages/virtualconf/mainstage/[id].js
Original file line number Diff line number Diff line change
@@ -1,40 +1,61 @@
import Head from 'next/head';
import { useState } from 'react';
import { Container, Button } from 'react-bootstrap';
import Videostreamer from '../../../components/clientsideonly/videostreamer';
import InAppChat from '../../../components/inappchat/inappchat';
import { useMediaQuery } from '@rocket.chat/fuselage-hooks';
import styles from '../../../styles/Videostreamer.module.css';
import Head from "next/head";
import { useEffect, useState } from "react";
import { Container, Button } from "react-bootstrap";
import Videostreamer from "../../../components/clientsideonly/videostreamer";
import InAppChat from "../../../components/inappchat/inappchat";
import { useMediaQuery } from "@rocket.chat/fuselage-hooks";
import styles from "../../../styles/Videostreamer.module.css";
import { FaRocketchat } from "react-icons/fa";
import { getIPInfo } from "../../../lib/geoAPI";

const rid = "GENERAL";
const host = process.env.NODE_ENV === "development" ? "http://localhost:3000" : "https://community.liaison.rocketchat.digital";
const asiaLink = process.env.NEXT_PUBLIC_SERVER_STREAM_LINK0
const otherLink = process.env.NEXT_PUBLIC_SERVER_STREAM_LINK1

const videoStreamerSrc = process.env.NEXT_PUBLIC_ROCKET_CHAT_GREENROOM_VIDEOSTREAMER_SRC

export default function ConfMainStage({ cookies }) {
export default function ConfMainStage({ cookies, ipInfo }) {
const [openChat, setOpenChat] = useState(true);
const isSmallScreen = useMediaQuery('(max-width: 992px)');
const isSmallScreen = useMediaQuery("(max-width: 992px)");
const [streamLink, setStreamLink] = useState(asiaLink);

const handleOpenChat = () => {
setOpenChat((prevState) => !prevState);
};

useEffect(() => {
try {
if (
ipInfo == null ||
ipInfo.timezone == undefined ||
ipInfo.timezone == null
) {
return;
}
if (ipInfo.timezone.split("/")[0] == "Asia") {
setStreamLink(asiaLink);
} else {
setStreamLink(otherLink);
}
} catch {
return;
}
}, []);

return (
<>
<div className='d-flex flex-column flex-lg-row '>
<div className="d-flex flex-column flex-lg-row ">
<Head>
<title>Virtual Conference Main Stage</title>
<meta
name='description'
content='Demonstration main stage for a virtual conference'
name="description"
content="Demonstration main stage for a virtual conference"
/>
</Head>
<Container fluid className={styles.videoContainer}>
<Videostreamer
poster='/gsocsmall.jpg'
src={videoStreamerSrc}
type='application/vnd.apple.mpegurl'
poster="/gsocsmall.jpg"
src={streamLink}
type="application/vnd.apple.mpegurl"
></Videostreamer>
</Container>
{isSmallScreen ? (
Expand All @@ -54,3 +75,13 @@ export default function ConfMainStage({ cookies }) {
</>
);
}

ConfMainStage.getInitialProps = async (ctx) => {
try {
const res = await getIPInfo();

return { ipInfo: res.data };
} catch {
return { ipInfo: null };
}
};
17 changes: 17 additions & 0 deletions docs/components/serverStreaming/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Setup Mainstage Video Source

To setup video source based on different geo IP server locations, visit [ipinfo.io](https://ipinfo.io/account/home) and from the dashboard grab the access token (if not registered, please create a account, the **Free** license provides 50k lookups per month) and add this token to the `.env.local` with the key `IPINFO_TOKEN`. Next, add two stream source links in the `env.local` which would be `NEXT_PUBLIC_SERVER_STREAM_LINK0` and `NEXT_PUBLIC_SERVER_STREAM_LINK1`. The users accessing the site with an IP address from Asia region would be alloted the link `NEXT_PUBLIC_SERVER_STREAM_LINK0` and for other server IPs the link `NEXT_PUBLIC_SERVER_STREAM_LINK1` would be used.


---