From 02c0882d90601416080ee08424f287e2059345bb 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 | 37 ++++++-- 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, 211 insertions(+), 6 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 8470ff502..2125689a3 100644 --- a/ui/component/header/view.jsx +++ b/ui/component/header/view.jsx @@ -99,6 +99,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 @@ -275,6 +276,11 @@ const Header = (props: Props) => { history={history} handleThemeToggle={handleThemeToggle} currentTheme={currentTheme} + activeChannelUrl={activeChannelUrl} + openSignOutModal={openSignOutModal} + email={email} + signOut={signOut} + livestreamEnabled={livestreamEnabled} /> )} @@ -391,10 +397,26 @@ type HeaderMenuButtonProps = { history: { push: (string) => void }, handleThemeToggle: (string) => void, currentTheme: string, + activeChannelUrl: ?string, + openSignOutModal: () => void, + email: ?string, + signOut: () => void, + livestreamEnabled: boolean, }; function HeaderMenuButtons(props: HeaderMenuButtonProps) { - const { authenticated, notificationsEnabled, history, handleThemeToggle, currentTheme } = props; + const { + authenticated, + notificationsEnabled, + history, + handleThemeToggle, + currentTheme, + activeChannelUrl, + openSignOutModal, + email, + signOut, + livestreamEnabled, + } = props; return (
@@ -422,10 +444,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 63918e4d5..738e81a9d 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 68d753cda..20c951986 100644 --- a/ui/constants/pages.js +++ b/ui/constants/pages.js @@ -60,4 +60,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 && ( + +
+ } + /> + )} + + {!fetchingChannels && activeChannelClaim && ( + + {/* Channel Selector */} + + + {/* Display StreamKey */} + { streamKey + ? (
+ {/* Stream Server Address */} + + + {/* Stream Key */} + +
) + : ( +
+
{JSON.stringify(activeChannelClaim)}
+ { sigData && +
{JSON.stringify(sigData)}
+ } +
+ ) + } + + {/* Stream Claim(s) */} + { livestreamClaim ? ( +
+

Your LiveStream Claims

+ +
+ ) : ( +
+
You must first publish a livestream claim before your stream will be visible!
+
TODO: add a button for this
+
+ )} + + {activeChannelClaim && +
Public Key: {activeChannelClaim.value.public_key}
+ } +
+ )} + + ); +}