Skip to content

Commit

Permalink
Expanded Playback and List controls (#6921)
Browse files Browse the repository at this point in the history
* Dont show countdown on Lists

* Add Repeat icon

* Add Shuffle icon

* Add Replay Icon

* Add Replay Option to autoplayCountdown

* Add Loop Control for Lists

* Add Shuffle control for Lists

* Improve View List Link and Fetch action

* Add Play Button to List page

* Add Shuffle Play Option on List Page and Menus

* Fix Modal Remove Collection I18n

* CSS: Fix Large list titles

* Fix List playback on Floating Player

* Add Theater Mode to its own class and fix bar text display

* Add Play Next VJS component

* Add Play Next Button

* Add Play Previous VJS Component

* Add Play Previous Button

* Add Autoplay Next Button

* Add separate control for autoplay next in list

* Bump redux

* Update CHANGELOG.md
  • Loading branch information
rafael-xmr committed Sep 2, 2021
1 parent 061e4dd commit 64cbd4a
Show file tree
Hide file tree
Showing 41 changed files with 1,030 additions and 300 deletions.
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

0 comments on commit 64cbd4a

Please sign in to comment.