From 464f530264f46f3461075aefa5327f813eb01b30 Mon Sep 17 00:00:00 2001 From: DispatchCommit Date: Mon, 1 Mar 2021 18:07:10 -0800 Subject: [PATCH] Create livestream page and generate signed streamkey --- ui/component/header/view.jsx | 17 +++- ui/component/router/view.jsx | 2 + ui/constants/pages.js | 2 +- ui/page/livestream/index.js | 13 +++ ui/page/livestream/view.jsx | 163 +++++++++++++++++++++++++++++++++++ 5 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 ui/page/livestream/index.js create mode 100644 ui/page/livestream/view.jsx diff --git a/ui/component/header/view.jsx b/ui/component/header/view.jsx index 00bb5b348..7aa7b5aa7 100644 --- a/ui/component/header/view.jsx +++ b/ui/component/header/view.jsx @@ -100,6 +100,7 @@ const Header = (props: Props) => { const hasBackout = Boolean(backout); const { backLabel, backNavDefault, title: backTitle, simpleTitle: simpleBackTitle } = backout || {}; const notificationsEnabled = (user && user.experimental_ui) || false; + const livestreamEnabled = (user && user.experimental_ui) || false; const activeChannelUrl = activeChannelClaim && activeChannelClaim.permanent_url; // Sign out if they click the "x" when they are on the password prompt @@ -280,6 +281,7 @@ const Header = (props: Props) => { openSignOutModal={openSignOutModal} email={email} signOut={signOut} + livestreamEnabled={livestreamEnabled} /> )} @@ -333,6 +335,7 @@ type HeaderMenuButtonProps = { openSignOutModal: () => void, email: ?string, signOut: () => void, + livestreamEnabled: boolean, }; function HeaderMenuButtons(props: HeaderMenuButtonProps) { @@ -346,6 +349,7 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) { openSignOutModal, email, signOut, + livestreamEnabled, } = props; return ( @@ -374,10 +378,15 @@ function HeaderMenuButtons(props: HeaderMenuButtonProps) { {__('New Channel')} - history.push(`/$/${PAGES.GO_LIVE}`)}> - - {__('Go Live')} - + + {/* Go Live Button for LiveStreaming */} + {(livestreamEnabled) &&( + history.push(`/$/${PAGES.LIVESTREAM}`)}> + + {__('Go Live')} + + )} + )} diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 4fe537452..5a0d3942e 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -36,6 +36,7 @@ import PasswordResetPage from 'page/passwordReset'; import PasswordSetPage from 'page/passwordSet'; import SignInVerifyPage from 'page/signInVerify'; import ChannelsPage from 'page/channels'; +import LiveStreamPage from 'page/livestream'; import EmbedWrapperPage from 'page/embedWrapper'; import TopPage from 'page/top'; import Welcome from 'page/welcome'; @@ -275,6 +276,7 @@ function AppRouter(props: Props) { + diff --git a/ui/constants/pages.js b/ui/constants/pages.js index d79eebf4c..a5f3e1e43 100644 --- a/ui/constants/pages.js +++ b/ui/constants/pages.js @@ -48,4 +48,4 @@ exports.BUY = 'buy'; exports.CHANNEL_NEW = 'channel/new'; exports.NOTIFICATIONS = 'notifications'; exports.YOUTUBE_SYNC = 'youtube'; -exports.GO_LIVE = 'livestream'; +exports.LIVESTREAM = 'livestream'; diff --git a/ui/page/livestream/index.js b/ui/page/livestream/index.js new file mode 100644 index 000000000..9c724308c --- /dev/null +++ b/ui/page/livestream/index.js @@ -0,0 +1,13 @@ +import { connect } from 'react-redux'; +import { selectMyChannelClaims, selectFetchingMyChannels } from 'lbry-redux'; +import { selectActiveChannelClaim } from 'redux/selectors/app'; +import { doSetActiveChannel } from 'redux/actions/app'; +import CreatorDashboardPage from './view'; + +const select = state => ({ + channels: selectMyChannelClaims(state), + fetchingChannels: selectFetchingMyChannels(state), + activeChannelClaim: selectActiveChannelClaim(state), +}); + +export default connect(select, { doSetActiveChannel })(CreatorDashboardPage); diff --git a/ui/page/livestream/view.jsx b/ui/page/livestream/view.jsx new file mode 100644 index 000000000..a956cb740 --- /dev/null +++ b/ui/page/livestream/view.jsx @@ -0,0 +1,163 @@ +// @flow +import * as PAGES from 'constants/pages'; +import React from 'react'; +import Page from 'component/page'; +import Spinner from 'component/spinner'; +import Button from 'component/button'; +import ChannelSelector from 'component/channelSelector'; +import Yrbl from 'component/yrbl'; +import { Lbry } from 'lbry-redux'; +import { toHex } from '../../util/hex'; +import ClaimPreview from '../../component/claimPreview'; +import { FormField } from '../../component/common/form'; + +type Props = { + channels: Array, + fetchingChannels: boolean, + activeChannelClaim: ?ChannelClaim, +}; + +export default function CreatorDashboardPage(props: Props) { + const { channels, fetchingChannels, activeChannelClaim } = props; + + const [sigData, setSigData] = React.useState({ signature: undefined, signing_ts: undefined }); + + const hasChannels = channels && channels.length > 0; + const activeChannelClaimStr = JSON.stringify(activeChannelClaim); + const streamKey = createStreamKey(); + + React.useEffect(() => { + if (activeChannelClaimStr) { + const channelClaim = JSON.parse(activeChannelClaimStr); + + // ensure we have a channel + if (channelClaim.claim_id) { + Lbry.channel_sign({ + channel_id: channelClaim.claim_id, + hexdata: toHex(channelClaim.name), + }) + .then((data) => { + console.log(data); + setSigData(data); + }) + .catch((error) => { + setSigData({ signature: null, signing_ts: null }); + console.error(error); + }); + } + } + }, [ activeChannelClaimStr, setSigData ]); + + function createStreamKey() { + if (!activeChannelClaim || !sigData.signature || !sigData.signing_ts) return null; + return `${activeChannelClaim.claim_id}?sig=${sigData.signature}&ts=${sigData.signing_ts}`; + } + + /******/ + + const LIVE_STREAM_TAG = 'odysee-livestream'; + + const [isFetching, setIsFetching] = React.useState(true); + const [isLive, setIsLive] = React.useState(false); + const [livestreamClaim, setLivestreamClaim] = React.useState(false); + + React.useEffect(() => { + if (!activeChannelClaimStr) return; + + const channelClaim = JSON.parse(activeChannelClaimStr); + + Lbry.claim_search({ + channel_ids: [channelClaim.claim_id], + any_tags: [LIVE_STREAM_TAG], + claim_type: ['stream'], + }) + .then((res) => { + if (res && res.items && res.items.length > 0) { + const claim = res.items[0]; + setLivestreamClaim(claim); + } else { + setIsFetching(false); + } + }) + .catch(() => { + setIsFetching(false); + }); + }, [activeChannelClaimStr]); + + return ( + + {fetchingChannels && ( +
+ +
+ )} + + {!fetchingChannels && !hasChannels && ( + +