123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- /* eslint-disable @next/next/no-css-tags */
- import React, { useState, useEffect, useContext, ReactElement } from 'react';
- import { Skeleton, Card, Statistic, Row, Col } from 'antd';
- import { formatDistanceToNow, formatRelative } from 'date-fns';
- import dynamic from 'next/dynamic';
- import { ServerStatusContext } from '../../utils/server-status-context';
- import { LogTable } from '../../components/admin/LogTable';
- import { Offline } from '../../components/admin/Offline';
- import { StreamHealthOverview } from '../../components/admin/StreamHealthOverview';
- import { LOGS_WARN, fetchData, FETCH_INTERVAL } from '../../utils/apis';
- import { formatIPAddress, isEmptyObject } from '../../utils/format';
- import { NewsFeed } from '../../components/admin/NewsFeed';
- import { AdminLayout } from '../../components/layouts/AdminLayout';
- // Lazy loaded components
- const UserOutlined = dynamic(() => import('@ant-design/icons/UserOutlined'), {
- ssr: false,
- });
- const ClockCircleOutlined = dynamic(() => import('@ant-design/icons/ClockCircleOutlined'), {
- ssr: false,
- });
- function streamDetailsFormatter(streamDetails) {
- return (
- <ul className="statistics-list">
- <li>
- {streamDetails.videoCodec || 'Unknown'} @ {streamDetails.videoBitrate || 'Unknown'} kbps
- </li>
- <li>{streamDetails.framerate || 'Unknown'} fps</li>
- <li>
- {streamDetails.width} x {streamDetails.height}
- </li>
- </ul>
- );
- }
- export default function Home() {
- const serverStatusData = useContext(ServerStatusContext);
- const { broadcaster, serverConfig: configData } = serverStatusData || {};
- const { remoteAddr, streamDetails } = broadcaster || {};
- const encoder = streamDetails?.encoder || 'Unknown encoder';
- const [logsData, setLogs] = useState([]);
- const getLogs = async () => {
- try {
- const result = await fetchData(LOGS_WARN);
- setLogs(result);
- } catch (error) {
- console.log('==== error', error);
- }
- };
- const getMoreStats = () => {
- getLogs();
- };
- useEffect(() => {
- getMoreStats();
- let intervalId = null;
- intervalId = setInterval(getMoreStats, FETCH_INTERVAL);
- return () => {
- clearInterval(intervalId);
- };
- }, []);
- if (isEmptyObject(configData) || isEmptyObject(serverStatusData)) {
- return (
- <>
- <Skeleton active />
- <Skeleton active />
- <Skeleton active />
- </>
- );
- }
- if (!broadcaster) {
- return <Offline logs={logsData} config={configData} />;
- }
- // map out settings
- const videoQualitySettings = serverStatusData?.currentBroadcast?.outputSettings?.map(setting => {
- const { audioPassthrough, videoPassthrough, audioBitrate, videoBitrate, framerate } = setting;
- const audioSetting = audioPassthrough
- ? `${streamDetails.audioCodec || 'Unknown'}, ${streamDetails.audioBitrate} kbps`
- : `${audioBitrate || 'Unknown'} kbps`;
- const videoSetting = videoPassthrough
- ? `${streamDetails.videoBitrate || 'Unknown'} kbps, ${streamDetails.framerate} fps ${
- streamDetails.width
- } x ${streamDetails.height}`
- : `${videoBitrate || 'Unknown'} kbps, ${framerate} fps`;
- return (
- <div className="stream-details-item-container">
- <Statistic
- className="stream-details-item"
- title="Outbound Video Stream"
- value={videoSetting}
- />
- <Statistic
- className="stream-details-item"
- title="Outbound Audio Stream"
- value={audioSetting}
- />
- </div>
- );
- });
- // inbound
- const { viewerCount, sessionPeakViewerCount } = serverStatusData;
- const streamAudioDetailString = `${streamDetails.audioCodec}, ${
- streamDetails.audioBitrate || 'Unknown'
- } kbps`;
- const broadcastDate = new Date(broadcaster.time);
- return (
- <div className="home-container">
- <div className="sections-container">
- <div className="online-status-section">
- <Card size="small" type="inner" className="online-details-card">
- <Row gutter={[16, 16]} align="middle">
- <Col span={8} sm={24} md={8}>
- <Statistic
- title={`Stream started ${formatRelative(broadcastDate, Date.now())}`}
- value={formatDistanceToNow(broadcastDate)}
- prefix={<ClockCircleOutlined />}
- />
- </Col>
- <Col span={8} sm={24} md={8}>
- <Statistic title="Viewers" value={viewerCount} prefix={<UserOutlined />} />
- </Col>
- <Col span={8} sm={24} md={8}>
- <Statistic
- title="Peak viewer count"
- value={sessionPeakViewerCount}
- prefix={<UserOutlined />}
- />
- </Col>
- </Row>
- <StreamHealthOverview />
- </Card>
- </div>
- <Row gutter={[16, 16]} className="section stream-details-section">
- <Col className="stream-details" span={12} sm={24} md={24} lg={12}>
- <Card
- size="small"
- title="Outbound Stream Details"
- type="inner"
- className="outbound-details"
- >
- {videoQualitySettings}
- </Card>
- <Card size="small" title="Inbound Stream Details" type="inner">
- <Statistic
- className="stream-details-item"
- title="Input"
- value={`${encoder} ${formatIPAddress(remoteAddr)}`}
- />
- <Statistic
- className="stream-details-item"
- title="Inbound Video Stream"
- value={streamDetails}
- formatter={streamDetailsFormatter}
- />
- <Statistic
- className="stream-details-item"
- title="Inbound Audio Stream"
- value={streamAudioDetailString}
- />
- </Card>
- </Col>
- <Col span={12} xs={24} sm={24} md={24} lg={12}>
- <NewsFeed />
- </Col>
- </Row>
- </div>
- <br />
- <LogTable logs={logsData} pageSize={5} />
- </div>
- );
- }
- Home.getLayout = function getLayout(page: ReactElement) {
- return <AdminLayout page={page} />;
- };
|