diff --git a/static/app-strings.json b/static/app-strings.json
index 10caef396..8332d40d9 100644
--- a/static/app-strings.json
+++ b/static/app-strings.json
@@ -1201,4 +1201,4 @@
"Share this channel": "Share this channel",
"File preview": "File preview",
"Go Home": "Go Home"
-}
\ No newline at end of file
+}
diff --git a/ui/component/embedTextArea/view.jsx b/ui/component/embedTextArea/view.jsx
index 8d2daecd9..224754db9 100644
--- a/ui/component/embedTextArea/view.jsx
+++ b/ui/component/embedTextArea/view.jsx
@@ -11,14 +11,16 @@ type Props = {
doToast: ({ message: string }) => void,
label?: string,
claim: Claim,
+ includeStartTime: boolean,
+ startTime: number,
};
export default function EmbedTextArea(props: Props) {
- const { doToast, snackMessage, label, claim } = props;
+ const { doToast, snackMessage, label, claim, includeStartTime, startTime } = props;
const { claim_id: claimId, name } = claim;
const input = useRef();
- const streamUrl = generateEmbedUrl(name, claimId);
+ const streamUrl = generateEmbedUrl(name, claimId, includeStartTime, startTime);
let embedText = ``;
function copyToClipboard() {
@@ -47,6 +49,7 @@ export default function EmbedTextArea(props: Props) {
value={embedText || ''}
ref={input}
onFocus={onFocus}
+ readOnly
/>
diff --git a/ui/component/socialShare/index.js b/ui/component/socialShare/index.js
index b91dcdd6c..839fb5f42 100644
--- a/ui/component/socialShare/index.js
+++ b/ui/component/socialShare/index.js
@@ -2,12 +2,14 @@ import { connect } from 'react-redux';
import { makeSelectClaimForUri, makeSelectTitleForUri } from 'lbry-redux';
import SocialShare from './view';
import { selectUserInviteReferralCode, selectUser } from 'lbryinc';
+import { makeSelectContentPositionForUri } from 'redux/selectors/content';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
referralCode: selectUserInviteReferralCode(state),
user: selectUser(state),
title: makeSelectTitleForUri(props.uri)(state),
+ position: makeSelectContentPositionForUri(props.uri)(state),
});
export default connect(select)(SocialShare);
diff --git a/ui/component/socialShare/view.jsx b/ui/component/socialShare/view.jsx
index c403abb5b..068befc76 100644
--- a/ui/component/socialShare/view.jsx
+++ b/ui/component/socialShare/view.jsx
@@ -6,6 +6,9 @@ import CopyableText from 'component/copyableText';
import EmbedTextArea from 'component/embedTextArea';
import { generateDownloadUrl } from 'util/lbrytv';
import useIsMobile from 'effects/use-is-mobile';
+import { FormField } from 'component/common/form';
+import { hmsToSeconds, secondsToHms } from 'util/time';
+import { generateLbryUrl, generateLbryWebUrl, generateEncodedLbryURL, generateOpenDotLbryDotComUrl } from 'util/url';
const IOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
const SUPPORTS_SHARE_API = typeof navigator.share !== 'undefined';
@@ -16,29 +19,56 @@ type Props = {
webShareable: boolean,
referralCode: string,
user: any,
+ position: number,
};
function SocialShare(props: Props) {
- const { claim, title, referralCode, user, webShareable } = props;
+ const { claim, title, referralCode, user, webShareable, position } = props;
const [showEmbed, setShowEmbed] = React.useState(false);
const [showExtra, setShowExtra] = React.useState(false);
+ const [includeStartTime, setincludeStartTime]: [boolean, any] = React.useState(false);
+ const [startTime, setStartTime]: [string, any] = React.useState(secondsToHms(position));
+ const [startTimeSeconds, setStartTimeSeconds]: [number, any] = React.useState(Math.floor(position));
const isMobile = useIsMobile();
+ let canonicalUrl = 'lbry://';
+ let permanentUrl = 'lbry://';
+ let name = '';
+ let claimId = '';
+
+ if (claim) {
+ canonicalUrl = claim.canonical_url;
+ permanentUrl = claim.permanent_url;
+ name = claim.name;
+ claimId = claim.claim_id;
+ }
+
+ const isChannel = claim.value_type === 'channel';
+ const rewardsApproved = user && user.is_reward_approved;
+ const OPEN_URL = 'https://open.lbry.com/';
+ const lbryUrl: string = generateLbryUrl(canonicalUrl, permanentUrl);
+ const lbryWebUrl: string = generateLbryWebUrl(lbryUrl);
+ const [encodedLbryURL, setEncodedLbryURL]: [string, any] = React.useState(
+ generateEncodedLbryURL(OPEN_URL, lbryWebUrl, includeStartTime, startTime)
+ );
+ const [openDotLbryDotComUrl, setOpenDotLbryDotComUrl]: [string, any] = React.useState(
+ generateOpenDotLbryDotComUrl(
+ OPEN_URL,
+ lbryWebUrl,
+ canonicalUrl,
+ permanentUrl,
+ referralCode,
+ rewardsApproved,
+ includeStartTime,
+ startTime
+ )
+ );
+ const downloadUrl = `${generateDownloadUrl(name, claimId)}`;
+
if (!claim) {
return null;
}
- const { canonical_url: canonicalUrl, permanent_url: permanentUrl, name, claim_id: claimId } = claim;
- const isChannel = claim.value_type === 'channel';
- const rewardsApproved = user && user.is_reward_approved;
- const OPEN_URL = 'https://open.lbry.com/';
- const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
- const lbryWebUrl = lbryUrl.replace(/#/g, ':');
- const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
- const referralParam: string = referralCode && rewardsApproved ? `?r=${referralCode}` : '';
- const openDotLbryDotComUrl: string = `${OPEN_URL}${lbryWebUrl}${referralParam}`;
- const downloadUrl = `${generateDownloadUrl(name, claimId)}`;
-
function handleWebShareClick() {
if (navigator.share) {
navigator.share({
@@ -48,9 +78,54 @@ function SocialShare(props: Props) {
}
}
+ function handleTimeCheckboxChange(checked) {
+ setincludeStartTime(checked);
+ updateUrls(checked, startTimeSeconds);
+ }
+
+ function handleTimeChange(value) {
+ setStartTime(value);
+ const startSeconds = hmsToSeconds(value);
+ setStartTimeSeconds(startSeconds);
+ updateUrls(true, startSeconds);
+ }
+
+ function updateUrls(includeStartTime, startTime) {
+ setOpenDotLbryDotComUrl(
+ generateOpenDotLbryDotComUrl(
+ OPEN_URL,
+ lbryWebUrl,
+ canonicalUrl,
+ permanentUrl,
+ referralCode,
+ rewardsApproved,
+ includeStartTime,
+ startTime
+ )
+ );
+
+ setEncodedLbryURL(generateEncodedLbryURL(OPEN_URL, lbryWebUrl, includeStartTime, startTime));
+ }
+
return (
+
+ handleTimeCheckboxChange(!includeStartTime)}
+ checked={includeStartTime}
+ label={__('Start at')}
+ />
+ handleTimeChange(event.target.value)}
+ />
+
)}
- {showEmbed && }
+ {showEmbed && (
+
+ )}
{showExtra && (
diff --git a/ui/scss/component/section.scss b/ui/scss/component/section.scss
index 649819c74..959f8b8cf 100644
--- a/ui/scss/component/section.scss
+++ b/ui/scss/component/section.scss
@@ -138,3 +138,15 @@
.section__actions--no-margin {
margin-top: 0;
}
+
+.section__start-at {
+ display: flex;
+ margin-top: var(--spacing-large);
+ fieldset-section {
+ width: 6em;
+ margin-top: 0;
+ }
+ .checkbox {
+ margin-right: 10px;
+ }
+}
diff --git a/ui/util/lbrytv.js b/ui/util/lbrytv.js
index 9ff34c085..ec639cf2c 100644
--- a/ui/util/lbrytv.js
+++ b/ui/util/lbrytv.js
@@ -6,8 +6,9 @@ function generateStreamUrl(claimName, claimId) {
return `${LBRY_TV_STREAMING_API}/content/claims/${claimName}/${claimId}/stream`;
}
-function generateEmbedUrl(claimName, claimId) {
- return `${URL}/$/embed/${claimName}/${claimId}`;
+function generateEmbedUrl(claimName, claimId, includeStartTime, startTime) {
+ const queryParam = includeStartTime ? `?t=${startTime}` : '';
+ return `${URL}/$/embed/${claimName}/${claimId}${queryParam}`;
}
function generateDownloadUrl(claimName, claimId) {
diff --git a/ui/util/time.js b/ui/util/time.js
new file mode 100644
index 000000000..ca7127d1a
--- /dev/null
+++ b/ui/util/time.js
@@ -0,0 +1,34 @@
+// @flow
+
+export function secondsToHms(seconds: number) {
+ seconds = Math.floor(seconds);
+ var hours = Math.floor(seconds / 3600);
+ var minutes = Math.floor(seconds / 60) % 60;
+ var seconds = seconds % 60;
+
+ return [hours, minutes, seconds]
+ .map(v => (v < 10 ? '0' + v : v))
+ .filter((v, i) => v !== '00' || i > 0)
+ .join(':');
+}
+
+export function hmsToSeconds(str: string) {
+ let timeParts = str.split(':'),
+ seconds = 0,
+ multiplier = 1;
+
+ if (timeParts.length > 0) {
+ while (timeParts.length > 0) {
+ let nextPart = parseInt(timeParts.pop(), 10);
+ if (!Number.isInteger(nextPart)) {
+ nextPart = 0;
+ }
+ seconds += multiplier * nextPart;
+ multiplier *= 60;
+ }
+ } else {
+ seconds = 0;
+ }
+
+ return seconds;
+}
diff --git a/ui/util/url.js b/ui/util/url.js
index 427c7e8af..b325e47ea 100644
--- a/ui/util/url.js
+++ b/ui/util/url.js
@@ -70,3 +70,41 @@ exports.generateInitialUrl = hash => {
}
return url;
};
+
+exports.generateLbryUrl = (canonicalUrl, permanentUrl) => {
+ return canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
+};
+
+exports.generateLbryWebUrl = lbryUrl => {
+ return lbryUrl.replace(/#/g, ':');
+};
+
+exports.generateEncodedLbryURL = (openUrl, lbryWebUrl, includeStartTime, startTime) => {
+ const queryParam = includeStartTime ? `?t=${startTime}` : '';
+ const encodedPart = encodeURIComponent(`${lbryWebUrl}${queryParam}`);
+ return `${openUrl}${encodedPart}`;
+};
+
+exports.generateOpenDotLbryDotComUrl = (
+ openUrl,
+ lbryWebUrl,
+ canonicalUrl,
+ permanentUrl,
+ referralCode,
+ rewardsApproved,
+ includeStartTime,
+ startTime
+) => {
+ let urlParams = new URLSearchParams();
+ if (referralCode && rewardsApproved) {
+ urlParams.append('r', referralCode);
+ }
+
+ if (includeStartTime) {
+ urlParams.append('t', startTime.toString());
+ }
+
+ const urlParamsString = urlParams.toString();
+ const url = `${openUrl}${lbryWebUrl}` + (urlParamsString === '' ? '' : `?${urlParamsString}`);
+ return url;
+};