WIP: live stream kill switch (#209)
* WIP: live stream kill switch * Update hint layout / style * update livestream API endpoint * use the no-cors option
This commit is contained in:
parent
db12a4b991
commit
11d3f88654
6 changed files with 149 additions and 5 deletions
|
@ -24,6 +24,7 @@ import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
||||||
import PublishPending from 'component/publishPending';
|
import PublishPending from 'component/publishPending';
|
||||||
import ClaimMenuList from 'component/claimMenuList';
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
|
import ClaimPreviewReset from 'component/claimPreviewReset';
|
||||||
import ClaimPreviewLoading from './claim-preview-loading';
|
import ClaimPreviewLoading from './claim-preview-loading';
|
||||||
import ClaimPreviewHidden from './claim-preview-no-mature';
|
import ClaimPreviewHidden from './claim-preview-no-mature';
|
||||||
import ClaimPreviewNoContent from './claim-preview-no-content';
|
import ClaimPreviewNoContent from './claim-preview-no-content';
|
||||||
|
@ -480,6 +481,13 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{claimIsMine && isLivestream && (
|
||||||
|
<div className={'claim-preview__hints'}>
|
||||||
|
<ClaimPreviewReset />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{!hideMenu && <ClaimMenuList uri={uri} collectionId={listId} />}
|
{!hideMenu && <ClaimMenuList uri={uri} collectionId={listId} />}
|
||||||
</>
|
</>
|
||||||
</WrapperElement>
|
</WrapperElement>
|
||||||
|
|
18
ui/component/claimPreviewReset/index.js
Normal file
18
ui/component/claimPreviewReset/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectActiveChannelClaim } from 'redux/selectors/app';
|
||||||
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
import ClaimPreviewReset from './view';
|
||||||
|
|
||||||
|
const select = (state) => {
|
||||||
|
const { claim_id: channelId, name: channelName } = selectActiveChannelClaim(state) || {};
|
||||||
|
return {
|
||||||
|
channelName,
|
||||||
|
channelId,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const perform = (dispatch) => ({
|
||||||
|
doToast: (props) => dispatch(doToast(props)),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(select, perform)(ClaimPreviewReset);
|
87
ui/component/claimPreviewReset/view.jsx
Normal file
87
ui/component/claimPreviewReset/view.jsx
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import Lbry from 'lbry';
|
||||||
|
import { LIVESTREAM_KILL } from 'constants/livestream';
|
||||||
|
import { SITE_HELP_EMAIL } from 'config';
|
||||||
|
import { toHex } from 'util/hex';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import 'scss/component/claim-preview-reset.scss';
|
||||||
|
|
||||||
|
// @Todo: move out of component.
|
||||||
|
const getStreamData = async (channelId: string, channelName: string) => {
|
||||||
|
if (!channelId || !channelName) throw new Error('Invalid channel data provided.');
|
||||||
|
|
||||||
|
const channelNameHex = toHex(channelName);
|
||||||
|
let channelSignature;
|
||||||
|
|
||||||
|
try {
|
||||||
|
channelSignature = await Lbry.channel_sign({ channel_id: channelId, hexdata: channelNameHex });
|
||||||
|
if (!channelSignature || !channelSignature.signature || !channelSignature.signing_ts) {
|
||||||
|
throw new Error('Error getting channel signature.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
d: channelNameHex,
|
||||||
|
s: channelSignature.signature,
|
||||||
|
t: channelSignature.signing_ts,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// @Todo: move out of component.
|
||||||
|
const killStream = async (channelId: string, payload: any) => {
|
||||||
|
fetch(`${LIVESTREAM_KILL}/${channelId}`, {
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'no-cors',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: new URLSearchParams(payload),
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
if (!res.status === 200) throw new Error('Kill stream API failed.');
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channelId: string,
|
||||||
|
channelName: string,
|
||||||
|
doToast: ({ message: string, isError?: boolean }) => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ClaimPreviewReset = (props: Props) => {
|
||||||
|
const { channelId, channelName, doToast } = props;
|
||||||
|
|
||||||
|
const handleClick = async () => {
|
||||||
|
try {
|
||||||
|
const streamData = await getStreamData(channelId, channelName);
|
||||||
|
await killStream(channelId, streamData);
|
||||||
|
doToast({ message: __('Live stream successfully reset.'), isError: false });
|
||||||
|
} catch {
|
||||||
|
doToast({ message: __('There was an error resetting the live stream.'), isError: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className={'claimPreviewReset'}>
|
||||||
|
<span className={'claimPreviewReset__hint'}>
|
||||||
|
{__(
|
||||||
|
"If you're having trouble starting a stream or if your stream shows that you're live but aren't, try a reset. If the problem persists, please reach out at %SITE_HELP_EMAIL%.",
|
||||||
|
{ SITE_HELP_EMAIL }
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
button="primary"
|
||||||
|
label={__('Reset stream')}
|
||||||
|
className={'claimPreviewReset__button'}
|
||||||
|
onClick={handleClick}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ClaimPreviewReset;
|
|
@ -2,3 +2,4 @@ export const LIVESTREAM_EMBED_URL = 'https://player.live.odysee.com/odysee';
|
||||||
export const LIVESTREAM_LIVE_API = 'https://api.live.odysee.com/v1/odysee/live';
|
export const LIVESTREAM_LIVE_API = 'https://api.live.odysee.com/v1/odysee/live';
|
||||||
export const LIVESTREAM_REPLAY_API = 'https://api.live.odysee.com/v1/replays/odysee';
|
export const LIVESTREAM_REPLAY_API = 'https://api.live.odysee.com/v1/replays/odysee';
|
||||||
export const LIVESTREAM_RTMP_URL = 'rtmp://stream.odysee.com/live';
|
export const LIVESTREAM_RTMP_URL = 'rtmp://stream.odysee.com/live';
|
||||||
|
export const LIVESTREAM_KILL = 'https://api.stream.odysee.com/stream/kill';
|
||||||
|
|
|
@ -235,7 +235,6 @@
|
||||||
.claim-preview__actions {
|
.claim-preview__actions {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
justify-content: flex-end;
|
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -245,10 +244,6 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.claim-preview__actions {
|
|
||||||
margin-left: var(--spacing-m);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: $breakpoint-xsmall) {
|
@media (max-width: $breakpoint-xsmall) {
|
||||||
|
|
35
ui/scss/component/claim-preview-reset.scss
Normal file
35
ui/scss/component/claim-preview-reset.scss
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
@import '../init/vars';
|
||||||
|
|
||||||
|
.claimPreviewReset {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: var(--spacing-xs);
|
||||||
|
color: var(--color-text-subtitle);
|
||||||
|
font-size: var(--font-small);
|
||||||
|
flex-direction: column;
|
||||||
|
@media (min-width: $breakpoint-xsmall) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.claimPreviewReset__hint {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
@media (min-width: $breakpoint-xsmall) {
|
||||||
|
margin-right: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.claimPreviewReset__button {
|
||||||
|
margin-top: var(--spacing-s);
|
||||||
|
width: 100%;
|
||||||
|
.button__content {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
@media (min-width: $breakpoint-xsmall) {
|
||||||
|
margin-top: 0;
|
||||||
|
width: auto;
|
||||||
|
.button__content {
|
||||||
|
justify-content: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue