Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expanded Playback and List controls #6921

Merged
merged 22 commits into from Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Expand Up @@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Show on content page if a file is part of a playlist already _community pr!_([#6393](https://github.com/lbryio/lbry-desktop/pull/6393))
- Add filtering to playlists ([#6905](https://github.com/lbryio/lbry-desktop/pull/6905))
- Added direct replying to notifications _community pr!_ ([#6935](https://github.com/lbryio/lbry-desktop/pull/6935))
- Added "Replay" option on autoplay countdown ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added "Loop" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added "Shuffle" option on Lists ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added Play Next/Previous buttons (with shortcuts SHIFT+N/SHIFT+P) ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Added separate control for autoplay next on video player ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))

### Changed
- Use Canonical Url for copy link ([#6500](https://github.com/lbryio/lbry-desktop/pull/6500))
Expand All @@ -23,6 +28,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Use resolve for OG metadata instead of chainquery _community pr!_ ([#6787](https://github.com/lbryio/lbry-desktop/pull/6787))
- Improved clickability of notification links _community pr!_ ([#6711](https://github.com/lbryio/lbry-desktop/pull/6711))
- Changing the supported language from Filipino to Tagalog _community pr!_ ([#6951](https://github.com/lbryio/lbry-desktop/pull/6951))
- Don't show countdown to next item in list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))
- Changed "View List" popup option to link, so can be opened on a new tab ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))

### Fixed
- App now supports '#' and ':' for claimId separator ([#6496](https://github.com/lbryio/lbry-desktop/pull/6496))
Expand All @@ -42,6 +49,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Fix OG: "Unparsable data structure - Truncated Unicode character" _community pr!_ ([#6839](https://github.com/lbryio/lbry-desktop/pull/6839))
- Fix Paid embed warning overlay redirection button now links to odysee _community pr!_ ([#6819](https://github.com/lbryio/lbry-desktop/pull/6819))
- Fix comment section redirection to create channel _community pr!_ ([#6557](https://github.com/lbryio/lbry-desktop/pull/6557))
- Clicking on the title of a floating player will take you back to the list ([#6921](https://github.com/lbryio/lbry-desktop/pull/6921))

## [0.51.1] - [2021-06-26]

Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -152,7 +152,7 @@
"imagesloaded": "^4.1.4",
"json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#dc264ec50ca3208d940521bdac0b61352d913c95",
"lbry-redux": "lbryio/lbry-redux#12a2ffc708bed45ba8d5a46620dc3892aaf890f8",
"lbryinc": "lbryio/lbryinc#8f9a58bfc8312a65614fd7327661cdcc502c4e59",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",
Expand Down
39 changes: 6 additions & 33 deletions ui/component/autoplayCountdown/index.js
@@ -1,43 +1,16 @@
import { connect } from 'react-redux';
import { makeSelectClaimForUri, SETTINGS, COLLECTIONS_CONSTS, makeSelectNextUrlForCollectionAndUrl } from 'lbry-redux';
import { makeSelectClaimForUri } from 'lbry-redux';
import { withRouter } from 'react-router';
import { makeSelectIsPlayerFloating, makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { doSetPlayingUri, doPlayUri, clearPosition } from 'redux/actions/content';
import AutoplayCountdown from './view';
import { selectModal } from 'redux/selectors/app';

/*
AutoplayCountdown does not fetch it's own next content to play, it relies on <RecommendedContent> being rendered.
This is dumb but I'm just the guy who noticed -kj
*/
const select = (state, props) => {
const { location } = props;
const { search } = location;
const urlParams = new URLSearchParams(search);
const collectionId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID);
const select = (state, props) => ({
nextRecommendedClaim: makeSelectClaimForUri(props.nextRecommendedUri)(state),
modal: selectModal(state),
});

let nextRecommendedUri;
if (collectionId) {
nextRecommendedUri = makeSelectNextUrlForCollectionAndUrl(collectionId, props.uri)(state);
} else {
nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state);
}

return {
collectionId,
nextRecommendedUri,
nextRecommendedClaim: makeSelectClaimForUri(nextRecommendedUri)(state),
isFloating: makeSelectIsPlayerFloating(props.location)(state),
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
modal: selectModal(state),
};
};

export default withRouter(
connect(select, {
doSetPlayingUri,
doPlayUri,
clearPosition,
})(AutoplayCountdown)
);
export default withRouter(connect(select, null)(AutoplayCountdown));
65 changes: 17 additions & 48 deletions ui/component/autoplayCountdown/view.jsx
@@ -1,38 +1,32 @@
// @flow
import React, { useCallback } from 'react';
import React from 'react';
import Button from 'component/button';
import UriIndicator from 'component/uriIndicator';
import I18nMessage from 'component/i18nMessage';
import { formatLbryUrlForWeb } from 'util/url';
import { withRouter } from 'react-router';
import debounce from 'util/debounce';
import { COLLECTIONS_CONSTS } from 'lbry-redux';
import * as ICONS from 'constants/icons';

const DEBOUNCE_SCROLL_HANDLER_MS = 150;
const CLASSNAME_AUTOPLAY_COUNTDOWN = 'autoplay-countdown';

type Props = {
history: { push: (string) => void },
nextRecommendedClaim: ?StreamClaim,
nextRecommendedUri: string,
isFloating: boolean,
doSetPlayingUri: ({ uri: ?string }) => void,
doPlayUri: (string) => void,
modal: { id: string, modalProps: {} },
collectionId?: string,
clearPosition: (string) => void,
doNavigate: () => void,
doReplay: () => void,
};

function AutoplayCountdown(props: Props) {
const {
nextRecommendedUri,
nextRecommendedClaim,
doSetPlayingUri,
doPlayUri,
isFloating,
history: { push },
modal,
collectionId,
clearPosition,
doNavigate,
doReplay,
} = props;
const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title;

Expand All @@ -45,40 +39,6 @@ function AutoplayCountdown(props: Props) {
const anyModalPresent = modal !== undefined && modal !== null;
const isTimerPaused = timerPaused || anyModalPresent;

let navigateUrl;
if (nextTitle) {
navigateUrl = formatLbryUrlForWeb(nextRecommendedUri);
if (collectionId) {
const collectionParams = new URLSearchParams();
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId);
navigateUrl = navigateUrl + `?` + collectionParams.toString();
}
}

const doPlay = useCallback(
(uri) => {
if (collectionId) {
clearPosition(uri);
}
doSetPlayingUri({ uri });
doPlayUri(uri);
},
[clearPosition, doSetPlayingUri, doPlayUri]
);

const doNavigate = useCallback(() => {
if (!isFloating) {
if (navigateUrl) {
push(navigateUrl);
doPlay(nextRecommendedUri);
}
} else {
if (nextRecommendedUri) {
doPlay(nextRecommendedUri);
}
}
}, [navigateUrl, nextRecommendedUri, isFloating, doPlay, push]);

function shouldPauseAutoplay() {
const elm = document.querySelector(`.${CLASSNAME_AUTOPLAY_COUNTDOWN}`);
return elm && elm.getBoundingClientRect().top < 0;
Expand Down Expand Up @@ -118,7 +78,7 @@ function AutoplayCountdown(props: Props) {
return () => {
clearInterval(interval);
};
}, [timer, doNavigate, navigateUrl, push, timerCanceled, isTimerPaused, nextRecommendedUri]);
}, [timer, doNavigate, push, timerCanceled, isTimerPaused, nextRecommendedUri]);

if (timerCanceled || !nextRecommendedUri) {
return null;
Expand Down Expand Up @@ -149,6 +109,15 @@ function AutoplayCountdown(props: Props) {
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
</div>
)}
<Button
label={__('Replay?')}
button="link"
iconRight={ICONS.REPLAY}
onClick={() => {
setTimerCanceled(true);
doReplay();
}}
/>
</div>
</div>
</div>
Expand Down
20 changes: 17 additions & 3 deletions ui/component/claimMenuList/index.js
Expand Up @@ -9,6 +9,8 @@ import {
COLLECTIONS_CONSTS,
makeSelectEditedCollectionForId,
makeSelectClaimIsMine,
doFetchItemsInCollection,
makeSelectUrlsForCollectionId,
} from 'lbry-redux';
import { makeSelectChannelIsMuted } from 'redux/selectors/blocked';
import { doChannelMute, doChannelUnmute } from 'redux/actions/blocked';
Expand All @@ -28,16 +30,23 @@ import { doToast } from 'redux/actions/notifications';
import { doChannelSubscribe, doChannelUnsubscribe } from 'redux/actions/subscriptions';
import { makeSelectIsSubscribed } from 'redux/selectors/subscriptions';
import { selectUserVerifiedEmail } from 'redux/selectors/user';
import { selectListShuffle } from 'redux/selectors/content';
import { doSetPlayingUri, doToggleShuffleList } from 'redux/actions/content';
import ClaimPreview from './view';
import fs from 'fs';

const select = (state, props) => {
const claim = makeSelectClaimForUri(props.uri, false)(state);
const collectionId = props.collectionId;
const resolvedList = makeSelectUrlsForCollectionId(collectionId)(state);
const repostedClaim = claim && claim.reposted_claim;
const contentClaim = repostedClaim || claim;
const contentSigningChannel = contentClaim && contentClaim.signing_channel;
const contentPermanentUri = contentClaim && contentClaim.permanent_url;
const contentChannelUri = (contentSigningChannel && contentSigningChannel.permanent_url) || contentPermanentUri;
const shuffleList = selectListShuffle(state);
const shuffle = shuffleList && shuffleList.collectionId === collectionId && shuffleList.newUrls;
const playNextUri = shuffle && shuffle[0];

return {
claim,
Expand All @@ -60,10 +69,12 @@ const select = (state, props) => {
isSubscribed: makeSelectIsSubscribed(contentChannelUri, true)(state),
channelIsAdminBlocked: makeSelectChannelIsAdminBlocked(props.uri)(state),
isAdmin: selectHasAdminChannel(state),
claimInCollection: makeSelectCollectionForIdHasClaimUrl(props.collectionId, contentPermanentUri)(state),
isMyCollection: makeSelectCollectionIsMine(props.collectionId)(state),
editedCollection: makeSelectEditedCollectionForId(props.collectionId)(state),
claimInCollection: makeSelectCollectionForIdHasClaimUrl(collectionId, contentPermanentUri)(state),
isMyCollection: makeSelectCollectionIsMine(collectionId)(state),
editedCollection: makeSelectEditedCollectionForId(collectionId)(state),
isAuthenticated: Boolean(selectUserVerifiedEmail(state)),
resolvedList,
playNextUri,
};
};

Expand All @@ -90,6 +101,9 @@ const perform = (dispatch) => ({
doChannelSubscribe: (subscription) => dispatch(doChannelSubscribe(subscription)),
doChannelUnsubscribe: (subscription) => dispatch(doChannelUnsubscribe(subscription)),
doCollectionEdit: (collection, props) => dispatch(doCollectionEdit(collection, props)),
fetchCollectionItems: (collectionId) => dispatch(doFetchItemsInCollection({ collectionId })),
doSetPlayingUri: (uri) => dispatch(doSetPlayingUri({ uri })),
doToggleShuffleList: (collectionId) => dispatch(doToggleShuffleList(undefined, collectionId, true, true)),
});

export default connect(select, perform)(ClaimPreview);
48 changes: 46 additions & 2 deletions ui/component/claimMenuList/view.jsx
Expand Up @@ -7,7 +7,7 @@ import React from 'react';
import classnames from 'classnames';
import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button';
import Icon from 'component/common/icon';
import { generateShareUrl, generateRssUrl, generateLbryContentUrl } from 'util/url';
import { generateShareUrl, generateRssUrl, generateLbryContentUrl, formatLbryUrlForWeb } from 'util/url';
import { useHistory } from 'react-router';
import { buildURI, parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';

Expand Down Expand Up @@ -56,6 +56,11 @@ type Props = {
isChannelPage: boolean,
editedCollection: Collection,
isAuthenticated: boolean,
playNextUri: string,
resolvedList: boolean,
fetchCollectionItems: (string) => void,
doSetPlayingUri: (string) => void,
doToggleShuffleList: (string) => void,
};

function ClaimMenuList(props: Props) {
Expand Down Expand Up @@ -93,7 +98,13 @@ function ClaimMenuList(props: Props) {
isChannelPage = false,
editedCollection,
isAuthenticated,
playNextUri,
resolvedList,
fetchCollectionItems,
doSetPlayingUri,
doToggleShuffleList,
} = props;
const [doShuffle, setDoShuffle] = React.useState(false);
const incognitoClaim = contentChannelUri && !contentChannelUri.includes('@');
const isChannel = !incognitoClaim && !contentSigningChannel;
const { channelName } = parseURI(contentChannelUri);
Expand All @@ -107,6 +118,27 @@ function ClaimMenuList(props: Props) {
: __('Follow');

const { push, replace } = useHistory();

const fetchItems = React.useCallback(() => {
if (collectionId) {
fetchCollectionItems(collectionId);
}
}, [collectionId, fetchCollectionItems]);

React.useEffect(() => {
if (doShuffle && resolvedList) {
doToggleShuffleList(collectionId);
if (playNextUri) {
const collectionParams = new URLSearchParams();
collectionParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, collectionId);
const navigateUrl = formatLbryUrlForWeb(playNextUri) + `?` + collectionParams.toString();
setDoShuffle(false);
doSetPlayingUri(playNextUri);
push(navigateUrl);
}
}
}, [collectionId, doSetPlayingUri, doShuffle, doToggleShuffleList, playNextUri, push, resolvedList]);

if (!claim) {
return null;
}
Expand Down Expand Up @@ -246,9 +278,21 @@ function ClaimMenuList(props: Props) {
{collectionId && isCollectionClaim ? (
<>
<MenuItem className="comment__menu-option" onSelect={() => push(`/$/${PAGES.LIST}/${collectionId}`)}>
<div className="menu__link">
<a className="menu__link" href={`/$/${PAGES.LIST}/${collectionId}`}>
<Icon aria-hidden icon={ICONS.VIEW} />
{__('View List')}
</a>
</MenuItem>
<MenuItem
className="comment__menu-option"
onSelect={() => {
if (!resolvedList) fetchItems();
setDoShuffle(true);
}}
>
<div className="menu__link">
<Icon aria-hidden icon={ICONS.SHUFFLE} />
{__('Shuffle Play')}
</div>
</MenuItem>
{isMyCollection && (
Expand Down