add video theater mode button

This commit is contained in:
Sean Yesmunt 2021-01-08 10:21:27 -05:00
parent b43593a996
commit d43c4d053e
19 changed files with 206 additions and 24 deletions

View file

@ -26,6 +26,7 @@ const select = (state, props) => {
streamingUrl: makeSelectStreamingUrlForUri(uri)(state),
floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
renderMode: makeSelectFileRenderModeForUri(uri)(state),
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
};
};

View file

@ -31,6 +31,7 @@ type Props = {
renderMode: string,
playingUri: ?PlayingUri,
primaryUri: ?string,
videoTheaterMode: boolean,
};
export default function FileRenderFloating(props: Props) {
@ -45,6 +46,7 @@ export default function FileRenderFloating(props: Props) {
renderMode,
playingUri,
primaryUri,
videoTheaterMode,
} = props;
const {
location: { pathname },
@ -187,7 +189,7 @@ export default function FileRenderFloating(props: Props) {
window.removeEventListener('resize', handleResize);
onFullscreenChange(window, 'remove', handleResize);
};
}, [setFileViewerRect, isFloating, playingUriSource, mainFilePlaying]);
}, [setFileViewerRect, isFloating, playingUriSource, mainFilePlaying, videoTheaterMode]);
useEffect(() => {
// @if TARGET='app'
@ -248,6 +250,7 @@ export default function FileRenderFloating(props: Props) {
className={classnames('content__viewer', {
'content__viewer--floating': isFloating,
'content__viewer--inline': !isFloating,
'content__viewer--theater-mode': !isFloating && videoTheaterMode,
})}
style={
!isFloating && fileViewerRect

View file

@ -32,6 +32,7 @@ type Props = {
claim: StreamClaim,
claimWasPurchased: boolean,
authenticated: boolean,
videoTheaterMode: boolean,
};
export default function FileRenderInitiator(props: Props) {
@ -51,6 +52,7 @@ export default function FileRenderInitiator(props: Props) {
costInfo,
claimWasPurchased,
authenticated,
videoTheaterMode,
} = props;
const cost = costInfo && costInfo.cost;
const isFree = hasCostInfo && cost === 0;
@ -118,6 +120,7 @@ export default function FileRenderInitiator(props: Props) {
style={thumbnail && !obscurePreview ? { backgroundImage: `url("${thumbnail}")` } : {}}
className={classnames('content__cover', {
'content__cover--disabled': disabled,
'content__cover--theater-mode': videoTheaterMode,
'card__media--nsfw': obscurePreview,
})}
>

View file

@ -1,4 +1,10 @@
import { connect } from 'react-redux';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { SETTINGS } from 'lbry-redux';
import Page from './view';
export default connect()(Page);
const select = (state, props) => ({
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
});
export default connect(select)(Page);

View file

@ -26,6 +26,7 @@ type Props = {
noFooter: boolean,
noSideNavigation: boolean,
fullWidthPage: boolean,
videoTheaterMode: boolean,
backout: {
backLabel?: string,
backNavDefault?: string,
@ -45,7 +46,9 @@ function Page(props: Props) {
noFooter = false,
noSideNavigation = false,
backout,
videoTheaterMode,
} = props;
const {
location: { pathname },
} = useHistory();
@ -82,7 +85,12 @@ function Page(props: Props) {
setSidebarOpen={setSidebarOpen}
/>
)}
<div className={classnames('main-wrapper__inner', { 'main-wrapper__inner--filepage': isOnFilePage })}>
<div
className={classnames('main-wrapper__inner', {
'main-wrapper__inner--filepage': isOnFilePage,
'main-wrapper__inner--theater-mode': isOnFilePage && videoTheaterMode,
})}
>
{!authPage && !noSideNavigation && (
<SideNavigation
sidebarOpen={sidebarOpen}
@ -96,6 +104,7 @@ function Page(props: Props) {
'main--full-width': fullWidthPage,
'main--auth-page': authPage,
'main--file-page': filePage,
'main--theater-mode': isOnFilePage && videoTheaterMode,
})}
>
{children}

View file

@ -55,7 +55,6 @@ export default function RecommendedContent(props: Props) {
title={__('Related')}
body={
<ClaimList
isCardBody
type="small"
loading={isSearching}
uris={recommendedContent}

View file

@ -8,6 +8,7 @@ import VideoViewer from './view';
import { withRouter } from 'react-router';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { makeSelectClientSetting } from 'redux/selectors/settings';
import { toggleVideoTheaterMode } from 'redux/actions/settings';
const select = (state, props) => {
const { search } = props.location;
@ -35,6 +36,7 @@ const perform = dispatch => ({
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
doAnalyticsBuffer: (uri, bufferData) => dispatch(doAnalyticsBuffer(uri, bufferData)),
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
toggleVideoTheaterMode: () => dispatch(toggleVideoTheaterMode()),
});
export default withRouter(connect(select, perform)(VideoViewer));

View file

@ -0,0 +1,16 @@
// @flow
import type { Player } from './videojs';
export function addTheaterModeButton(player: Player, toggleVideoTheaterMode: () => void) {
var myButton = player.controlBar.addChild('button', {
text: __('Theater mode'),
clickHandler: () => {
toggleVideoTheaterMode();
},
});
// $FlowFixMe
myButton.addClass('vjs-button--theater-mode');
// $FlowFixMe
myButton.setAttribute('title', __('Theater mode'));
}

View file

@ -31,6 +31,9 @@ export type Player = {
userActive: (?boolean) => boolean,
overlay: any => void,
mobileUi: any => void,
controlBar: {
addChild: (string, any) => void,
},
};
type Props = {

View file

@ -3,7 +3,6 @@ import React, { useEffect, useState, useContext, useCallback } from 'react';
import { stopContextMenu } from 'util/context-menu';
import type { Player } from './internal/videojs';
import VideoJs from './internal/videojs';
import analytics from 'analytics';
import { EmbedContext } from 'page/embedWrapper/view';
import classnames from 'classnames';
@ -13,6 +12,7 @@ import usePrevious from 'effects/use-previous';
import FileViewerEmbeddedEnded from 'web/component/fileViewerEmbeddedEnded';
import FileViewerEmbeddedTitle from 'component/fileViewerEmbeddedTitle';
import LoadingScreen from 'component/common/loading-screen';
import { addTheaterModeButton } from './internal/theater-mode';
const PLAY_TIMEOUT_ERROR = 'play_timeout_error';
@ -35,6 +35,7 @@ type Props = {
claimRewards: () => void,
savePosition: (string, number) => void,
clearPosition: string => void,
toggleVideoTheaterMode: () => void,
};
/*
@ -62,6 +63,7 @@ function VideoViewer(props: Props) {
savePosition,
clearPosition,
desktopPlayStartTime,
toggleVideoTheaterMode,
} = props;
const claimId = claim && claim.claim_id;
const isAudio = contentType.includes('audio');
@ -135,6 +137,8 @@ function VideoViewer(props: Props) {
if (!embedded) {
player.muted(muted);
player.volume(volume);
addTheaterModeButton(player, toggleVideoTheaterMode);
}
const shouldPlay = !embedded || autoplayIfEmbedded;

View file

@ -1,9 +1,15 @@
import { connect } from 'react-redux';
import { doSetContentHistoryItem, doSetPrimaryUri } from 'redux/actions/content';
import { withRouter } from 'react-router';
import { doFetchFileInfo, makeSelectFileInfoForUri, makeSelectMetadataForUri, makeSelectClaimIsNsfw } from 'lbry-redux';
import {
doFetchFileInfo,
makeSelectFileInfoForUri,
makeSelectMetadataForUri,
makeSelectClaimIsNsfw,
SETTINGS,
} from 'lbry-redux';
import { makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowMatureContent } from 'redux/selectors/settings';
import { selectShowMatureContent, makeSelectClientSetting } from 'redux/selectors/settings';
import { makeSelectFileRenderModeForUri } from 'redux/selectors/content';
import { makeSelectCommentForCommentId } from 'redux/selectors/comments';
import FilePage from './view';
@ -21,6 +27,7 @@ const select = (state, props) => {
isMature: makeSelectClaimIsNsfw(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
renderMode: makeSelectFileRenderModeForUri(props.uri)(state),
videoTheaterMode: makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state),
};
};

View file

@ -24,6 +24,7 @@ type Props = {
isMature: boolean,
linkedComment: any,
setPrimaryUri: (?string) => void,
videoTheaterMode: boolean,
};
function FilePage(props: Props) {
@ -39,6 +40,7 @@ function FilePage(props: Props) {
costInfo,
linkedComment,
setPrimaryUri,
videoTheaterMode,
} = props;
const cost = costInfo ? costInfo.cost : null;
const hasFileInfo = fileInfo !== undefined;
@ -67,10 +69,9 @@ function FilePage(props: Props) {
return (
<React.Fragment>
<div className={PRIMARY_PLAYER_WRAPPER_CLASS}>
<FileRenderInitiator uri={uri} />
<FileRenderInitiator uri={uri} videoTheaterMode={videoTheaterMode} />
</div>
{/* playables will be rendered and injected by <FileRenderFloating> */}
<FileTitle uri={uri} />
</React.Fragment>
);
}
@ -96,14 +97,14 @@ function FilePage(props: Props) {
return (
<React.Fragment>
<FileRenderInitiator uri={uri} />
<FileRenderInitiator uri={uri} videoTheaterMode={videoTheaterMode} />
<FileRenderInline uri={uri} />
<FileTitle uri={uri} />
</React.Fragment>
);
}
function renderBlockedPage() {
if (obscureNsfw && isMature) {
return (
<Page>
<FileTitle uri={uri} isNsfwBlocked />
@ -111,19 +112,21 @@ function FilePage(props: Props) {
);
}
if (obscureNsfw && isMature) {
return renderBlockedPage();
}
return (
<Page className="file-page" filePage>
<div className={classnames('section card-stack', `file-page__${renderMode}`)}>
{renderFilePageLayout()}
<div className="file-page__secondary-content">
<div>
{RENDER_MODES.FLOATING_MODES.includes(renderMode) && <FileTitle uri={uri} />}
<CommentsList uri={uri} linkedComment={linkedComment} />
</div>
{videoTheaterMode && <RecommendedContent uri={uri} />}
</div>
</div>
<RecommendedContent uri={uri} />
{!videoTheaterMode && <RecommendedContent uri={uri} />}
</Page>
);
}

View file

@ -414,3 +414,12 @@ export function doSetAppToTrayWhenClosed(value) {
dispatch(doSetClientSetting(SETTINGS.TO_TRAY_WHEN_CLOSED, value));
};
}
export function toggleVideoTheaterMode() {
return (dispatch, getState) => {
const state = getState();
const videoTheaterMode = makeSelectClientSetting(SETTINGS.VIDEO_THEATER_MODE)(state);
dispatch(doSetClientSetting(SETTINGS.VIDEO_THEATER_MODE, !videoTheaterMode));
};
}

View file

@ -46,6 +46,7 @@ const defaultState = {
[SETTINGS.OS_NOTIFICATIONS_ENABLED]: true,
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: false,
[SETTINGS.TILE_LAYOUT]: true,
[SETTINGS.VIDEO_THEATER_MODE]: false,
[SETTINGS.DARK_MODE_TIMES]: {
from: { hour: '21', min: '00', formattedTime: '21:00' },

View file

@ -94,6 +94,42 @@
}
}
.vjs-control-bar {
visibility: visible;
opacity: 1 !important;
transition: visibility 1s, opacity 1s;
}
.vjs-fullscreen-control {
order: 2;
}
.vjs-button--theater-mode.vjs-button {
display: none;
@media (min-width: $breakpoint-medium) {
display: block;
order: 1;
width: 1.25rem;
height: 1.25rem;
margin-top: 0.3rem;
background-repeat: no-repeat;
background-position: center;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 -2 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E");
}
&:focus:not(:focus-visible) {
// Need to repeat these styles because of videojs weirdness
background: black
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='14' viewBox='0 -2 24 24' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-monitor'%3E%3Crect x='2' y='3' width='20' height='14' rx='2' ry='2'%3E%3C/rect%3E%3Cline x1='8' y1='21' x2='16' y2='21'%3E%3C/line%3E%3Cline x1='12' y1='17' x2='12' y2='21'%3E%3C/line%3E%3C/svg%3E")
no-repeat center;
}
.vjs-icon-placeholder {
display: none;
}
}
.button--link {
color: var(--color-link);
transition: color 0.2s;

View file

@ -24,6 +24,12 @@
}
}
.content__viewer--theater-mode {
top: 0;
border-radius: 0;
border: none;
}
.content__wrapper {
position: relative;
width: 100%;
@ -33,6 +39,10 @@
.content__wrapper--floating {
height: var(--floating-viewer-height);
width: var(--floating-viewer-width);
.vjs-button--theater-mode {
display: none;
}
}
.content__actions {
@ -107,6 +117,11 @@
}
}
.content__cover--theater-mode {
@extend .content__cover;
border-radius: 0;
}
.content__cover--none {
@include thumbnail;
cursor: default;

View file

@ -39,6 +39,10 @@
padding-top: var(--spacing-s);
}
.main-wrapper__inner--theater-mode {
padding-top: 0;
}
.main {
position: relative;
width: calc(100% - var(--side-nav-width) - var(--spacing-l));
@ -65,15 +69,43 @@
align-items: flex-start;
padding-left: var(--spacing-m);
padding-right: var(--spacing-m);
position: relative;
> :first-child {
flex-grow: 2;
}
.file-page__secondary-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
margin-top: var(--spacing-m);
max-width: var(--page-max-width--filepage);
margin-left: auto;
margin-right: auto;
> :first-child {
flex: 1;
margin-right: var(--spacing-m);
}
@media (min-width: $breakpoint-medium) {
flex-direction: row;
}
}
.file-page__info {
margin-top: var(--spacing-m);
}
.file-page__recommended {
width: 25rem;
height: 0%;
width: 35rem;
margin-left: var(--spacing-m);
@media (max-width: $breakpoint-small) {
margin-left: 0;
}
@media (max-width: $breakpoint-medium) {
width: 100%;
@ -82,8 +114,6 @@
}
@media (max-width: $breakpoint-medium) {
flex-direction: column;
> :first-child {
margin-right: 0;
}
@ -95,13 +125,48 @@
}
}
.main--theater-mode {
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
width: 100vw;
max-width: none;
> :first-child {
margin-right: 0;
}
.file-page__info {
padding: 0 var(--spacing-m);
margin-top: var(--spacing-m);
max-width: var(--page-max-width--filepage);
display: flex;
flex-direction: column;
margin-left: auto;
margin-right: auto;
}
.file-page__recommended {
width: 25rem;
}
.file-page__secondary-content {
padding: 0 var(--spacing-s);
@media (min-width: $breakpoint-medium) {
flex-direction: row;
}
}
}
.main--full-width {
@extend .main;
@media (min-width: $breakpoint-large) {
max-width: none;
width: 100%;
padding: 0 var(--spacing-l);
margin: 0 var(--spacing-l);
}
}

View file

@ -108,6 +108,7 @@
display: flex;
align-items: center;
margin-top: var(--spacing-l);
margin-right: var(--spacing-s);
~ .section {
margin-top: var(--spacing-l);

View file

@ -1,5 +1,4 @@
:root {
// Button
--color-navigation-icon: var(--color-gray-5);
--color-link-active: var(--color-primary);
--color-navigation-link: var(--color-gray-5);