semi-broken
This commit is contained in:
parent
e97fe3db34
commit
793f622d8d
30 changed files with 574 additions and 431 deletions
7
lbrytv/component/fileViewerEmbeddedEnded/index.js
Normal file
7
lbrytv/component/fileViewerEmbeddedEnded/index.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import fileViewerEmbeddedEnded from './view';
|
||||
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||
|
||||
export default connect(state => ({
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
}))(fileViewerEmbeddedEnded);
|
55
lbrytv/component/fileViewerEmbeddedEnded/view.jsx
Normal file
55
lbrytv/component/fileViewerEmbeddedEnded/view.jsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { withRouter } from 'react-router';
|
||||
import { URL } from 'config';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
isAuthenticated: boolean,
|
||||
};
|
||||
|
||||
function fileViewerEmbeddedEnded(props: Props) {
|
||||
const { uri, isAuthenticated } = props;
|
||||
|
||||
const prompts = isAuthenticated
|
||||
? {
|
||||
discuss_auth: 'Continue the discussion on lbry.tv',
|
||||
tip_auth: 'Always tip your creators',
|
||||
}
|
||||
: {
|
||||
bigtech_unauth: 'Together, we can take back control from big tech',
|
||||
discuss_unauth: 'Continue the discussion on lbry.tv',
|
||||
find_unauth: 'Find more great content on lbry.tv',
|
||||
a_b_unauth: "We test a lot of messages here. Wouldn't it be funny if the one telling you that did the best?",
|
||||
earn_unauth: 'Join lbry.tv and earn to watch.',
|
||||
blockchain_unauth: "Now if anyone asks, you can say you've used a blockchain.",
|
||||
};
|
||||
|
||||
const promptKeys = Object.keys(prompts);
|
||||
const promptKey = promptKeys[Math.floor(Math.random() * promptKeys.length)];
|
||||
const prompt = prompts[promptKey];
|
||||
const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=${promptKey}`;
|
||||
|
||||
return (
|
||||
<div className="file-viewer__overlay">
|
||||
<div className="file-viewer__overlay-secondary">
|
||||
<Button
|
||||
className="file-viewer__overlay-logo file-viewer__embedded-title-logo"
|
||||
label="LBRY"
|
||||
icon={ICONS.LBRY}
|
||||
href={URL}
|
||||
/>
|
||||
</div>
|
||||
<div className="file-viewer__overlay-title">{prompt}</div>
|
||||
<div className="file-viewer__overlay-actions">
|
||||
<Button label={__('Rewatch or Discuss')} button="primary" href={lbrytvLink} />
|
||||
{!isAuthenticated && <Button label={__('Join lbry.tv')} button="secondary" href={`${URL}/$/signup`} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(fileViewerEmbeddedEnded);
|
9
lbrytv/component/fileViewerEmbeddedTitle/index.js
Normal file
9
lbrytv/component/fileViewerEmbeddedTitle/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { connect } from 'react-redux';
|
||||
import fileViewerEmbeddedTitle from './view';
|
||||
import { makeSelectTitleForUri } from 'lbry-redux';
|
||||
|
||||
export default connect((state, props) => {
|
||||
return {
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
};
|
||||
})(fileViewerEmbeddedTitle);
|
27
lbrytv/component/fileViewerEmbeddedTitle/view.jsx
Normal file
27
lbrytv/component/fileViewerEmbeddedTitle/view.jsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import { withRouter } from 'react-router';
|
||||
import { URL } from 'config';
|
||||
import * as ICONS from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
title: ?string,
|
||||
};
|
||||
|
||||
function fileViewerEmbeddedTitle(props: Props) {
|
||||
const { uri, title } = props;
|
||||
|
||||
const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=embed`;
|
||||
|
||||
return (
|
||||
<div className="file-viewer__embedded-title">
|
||||
<Button className="file-viewer__overlay-logo file-viewer__embedded-title-logo" icon={ICONS.LBRY} href={URL} />
|
||||
<Button label={title} button="link" href={lbrytvLink} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default withRouter(fileViewerEmbeddedTitle);
|
|
@ -51,7 +51,8 @@
|
|||
"electron-notarize": "^0.1.1",
|
||||
"electron-updater": "^4.2.4",
|
||||
"express": "^4.17.1",
|
||||
"if-env": "^1.0.4"
|
||||
"if-env": "^1.0.4",
|
||||
"videojs-logo": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
|
|
|
@ -284,7 +284,7 @@ function App(props: Props) {
|
|||
<React.Fragment>
|
||||
<Router />
|
||||
<ModalRouter />
|
||||
<FileRenderFloating pageUri={uri} />
|
||||
<FileRenderFloating />
|
||||
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
|
||||
|
||||
{/* @if TARGET='app' */}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import { makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
|
||||
import { makeSelectIsPlayerFloating, makeSelectNextUnplayedRecommended } from 'redux/selectors/content';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import RecommendedVideos from './view';
|
||||
import AutoplayCountdown from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
const nextRecommendedUri = makeSelectNextUnplayedRecommended(props.uri)(state);
|
||||
return {
|
||||
nextRecommendedUri,
|
||||
nextRecommendedClaim: makeSelectClaimForUri(nextRecommendedUri)(state),
|
||||
isFloating: makeSelectIsPlayerFloating(props.location)(state),
|
||||
autoplay: makeSelectClientSetting(SETTINGS.AUTOPLAY)(state),
|
||||
};
|
||||
};
|
||||
|
@ -19,7 +20,4 @@ const perform = dispatch => ({
|
|||
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(RecommendedVideos);
|
||||
export default connect(select, perform)(AutoplayCountdown);
|
||||
|
|
|
@ -10,6 +10,7 @@ type Props = {
|
|||
history: { push: string => void },
|
||||
nextRecommendedClaim: ?StreamClaim,
|
||||
nextRecommendedUri: string,
|
||||
isFloating: boolean,
|
||||
setPlayingUri: (string | null) => void,
|
||||
};
|
||||
|
||||
|
@ -18,10 +19,15 @@ function AutoplayCountdown(props: Props) {
|
|||
nextRecommendedUri,
|
||||
nextRecommendedClaim,
|
||||
setPlayingUri,
|
||||
isFloating,
|
||||
history: { push },
|
||||
} = props;
|
||||
const nextTitle = nextRecommendedClaim && nextRecommendedClaim.value && nextRecommendedClaim.value.title;
|
||||
const [timer, setTimer] = React.useState(5);
|
||||
|
||||
/* this value is coupled with CSS timing variables on .autoplay-countdown__timer */
|
||||
const countdownTime = 1000;
|
||||
|
||||
const [timer, setTimer] = React.useState(countdownTime);
|
||||
const [timerCanceled, setTimerCanceled] = React.useState(false);
|
||||
|
||||
let navigateUrl;
|
||||
|
@ -29,15 +35,24 @@ function AutoplayCountdown(props: Props) {
|
|||
navigateUrl = formatLbryUrlForWeb(nextRecommendedUri);
|
||||
}
|
||||
|
||||
function doNavigate() {
|
||||
// FIXME: make autoplay continue in floating player
|
||||
if (!isFloating) {
|
||||
// if not floating
|
||||
setPlayingUri(null);
|
||||
push(navigateUrl);
|
||||
} else {
|
||||
setPlayingUri(nextRecommendedUri);
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
let interval;
|
||||
if (!timerCanceled) {
|
||||
interval = setInterval(() => {
|
||||
const newTime = timer - 1;
|
||||
if (newTime === 0) {
|
||||
// Set the playingUri to null so the app doesn't try to make a floating window with the video that just finished
|
||||
setPlayingUri(null);
|
||||
push(navigateUrl);
|
||||
doNavigate();
|
||||
} else {
|
||||
setTimer(timer - 1);
|
||||
}
|
||||
|
@ -46,28 +61,31 @@ function AutoplayCountdown(props: Props) {
|
|||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [timer, navigateUrl, push, timerCanceled, setPlayingUri, nextRecommendedUri]);
|
||||
}, [timer, doNavigate, navigateUrl, push, timerCanceled, setPlayingUri, nextRecommendedUri]);
|
||||
|
||||
if (timerCanceled) {
|
||||
if (timerCanceled || !nextRecommendedUri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="video-overlay__wrapper">
|
||||
<div className="video-overlay__subtitle">
|
||||
<I18nMessage tokens={{ channel: <UriIndicator link uri={nextRecommendedUri} /> }}>
|
||||
Up Next by %channel%
|
||||
</I18nMessage>
|
||||
</div>
|
||||
<div className="video-overlay__title">{nextTitle}</div>
|
||||
<div className="file-viewer__overlay">
|
||||
<div className="autoplay-countdown">
|
||||
<div className="file-viewer__overlay-secondary">
|
||||
<I18nMessage tokens={{ channel: <UriIndicator link uri={nextRecommendedUri} /> }}>
|
||||
Up Next by %channel%
|
||||
</I18nMessage>
|
||||
</div>
|
||||
|
||||
<div className="video-overlay__actions">
|
||||
<div className="video-overlay__subtitle">
|
||||
{__('Playing in %seconds_left% seconds', { seconds_left: timer })}
|
||||
</div>
|
||||
<div className="section__actions--centered">
|
||||
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
|
||||
<div className="file-viewer__overlay-title">{nextTitle}</div>
|
||||
<div className="autoplay-countdown__timer">
|
||||
<div className={'autoplay-countdown__button autoplay-countdown__button--' + (timer % 5)}>
|
||||
<Button onClick={doNavigate} iconSize={30} title={__('Play')} className="button--icon button--play" />
|
||||
</div>
|
||||
<div className="file-viewer__overlay-secondary autoplay-countdown__counter">
|
||||
{__('Playing in %seconds_left% seconds', { seconds_left: timer })}
|
||||
</div>
|
||||
</div>
|
||||
<Button label={__('Cancel')} button="link" onClick={() => setTimerCanceled(true)} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -12,7 +12,6 @@ import {
|
|||
makeSelectFileExtensionForUri,
|
||||
makeSelectStreamingUrlForUriWebProxy,
|
||||
} from 'redux/selectors/content';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import FileRender from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
|
@ -30,8 +29,4 @@ const select = (state, props) => {
|
|||
};
|
||||
};
|
||||
|
||||
const perform = dispatch => ({
|
||||
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(FileRender);
|
||||
export default connect(select)(FileRender);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// @flow
|
||||
import { URL } from 'config';
|
||||
import { remote } from 'electron';
|
||||
import React, { Suspense } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
@ -7,10 +6,7 @@ import * as RENDER_MODES from 'constants/file_render_modes';
|
|||
import VideoViewer from 'component/viewers/videoViewer';
|
||||
import ImageViewer from 'component/viewers/imageViewer';
|
||||
import AppViewer from 'component/viewers/appViewer';
|
||||
import Button from 'component/button';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||
import { formatLbryUrlForWeb } from 'util/url';
|
||||
import fs from 'fs';
|
||||
|
||||
import DocumentViewer from 'component/viewers/documentViewer';
|
||||
|
@ -34,32 +30,15 @@ type Props = {
|
|||
downloadPath: string,
|
||||
fileExtension: string,
|
||||
autoplay: boolean,
|
||||
setPlayingUri: (string | null) => void,
|
||||
currentlyFloating: boolean,
|
||||
renderMode: string,
|
||||
thumbnail: string,
|
||||
onStartedCallback: () => void,
|
||||
};
|
||||
|
||||
type State = {
|
||||
showAutoplayCountdown: boolean,
|
||||
showEmbededMessage: boolean,
|
||||
};
|
||||
|
||||
class FileRender extends React.PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showAutoplayCountdown: false,
|
||||
showEmbededMessage: false,
|
||||
};
|
||||
|
||||
(this: any).escapeListener = this.escapeListener.bind(this);
|
||||
(this: any).onEndedAutoplay = this.onEndedAutoplay.bind(this);
|
||||
(this: any).onEndedEmbedded = this.onEndedEmbedded.bind(this);
|
||||
(this: any).getOnEndedCb = this.getOnEndedCb.bind(this);
|
||||
(this: any).onStartedCb = this.onStartedCb.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
@ -84,39 +63,6 @@ class FileRender extends React.PureComponent<Props, State> {
|
|||
remote.getCurrentWindow().setFullScreen(false);
|
||||
}
|
||||
|
||||
onStartedCb() {
|
||||
const { onStartedCallback } = this.props;
|
||||
|
||||
if (onStartedCallback) {
|
||||
onStartedCallback();
|
||||
}
|
||||
}
|
||||
|
||||
getOnEndedCb() {
|
||||
const { setPlayingUri, currentlyFloating, embedded } = this.props;
|
||||
|
||||
if (embedded) {
|
||||
return this.onEndedEmbedded;
|
||||
}
|
||||
|
||||
if (!currentlyFloating) {
|
||||
return this.onEndedAutoplay;
|
||||
}
|
||||
|
||||
return () => setPlayingUri(null);
|
||||
}
|
||||
|
||||
onEndedAutoplay() {
|
||||
const { autoplay } = this.props;
|
||||
if (autoplay) {
|
||||
this.setState({ showAutoplayCountdown: true });
|
||||
}
|
||||
}
|
||||
|
||||
onEndedEmbedded() {
|
||||
this.setState({ showEmbededMessage: true });
|
||||
}
|
||||
|
||||
renderViewer() {
|
||||
const { currentTheme, contentType, downloadPath, fileExtension, streamingUrl, uri, renderMode } = this.props;
|
||||
const source = streamingUrl;
|
||||
|
@ -124,15 +70,7 @@ class FileRender extends React.PureComponent<Props, State> {
|
|||
switch (renderMode) {
|
||||
case RENDER_MODES.AUDIO:
|
||||
case RENDER_MODES.VIDEO:
|
||||
return (
|
||||
<VideoViewer
|
||||
uri={uri}
|
||||
source={source}
|
||||
contentType={contentType}
|
||||
onEndedCb={this.getOnEndedCb()}
|
||||
onStartedCb={this.onStartedCb}
|
||||
/>
|
||||
);
|
||||
return <VideoViewer uri={uri} source={source} contentType={contentType} />;
|
||||
case RENDER_MODES.IMAGE:
|
||||
return <ImageViewer uri={uri} source={source} />;
|
||||
case RENDER_MODES.HTML:
|
||||
|
@ -177,30 +115,15 @@ class FileRender extends React.PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { uri, currentlyFloating, embedded, renderMode } = this.props;
|
||||
const { showAutoplayCountdown, showEmbededMessage } = this.state;
|
||||
const lbrytvLink = `${URL}${formatLbryUrlForWeb(uri)}?src=embed`;
|
||||
const { embedded, renderMode } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames({
|
||||
'file-render': !embedded,
|
||||
className={classnames('file-render', {
|
||||
'file-render--document': RENDER_MODES.TEXT_MODES.includes(renderMode) && !embedded,
|
||||
'file-render__embed': embedded,
|
||||
'file-render--embed': embedded,
|
||||
})}
|
||||
>
|
||||
{embedded && showEmbededMessage && (
|
||||
<div className="video-overlay__wrapper">
|
||||
<div className="video-overlay__title">{__('See more on lbry.tv')}</div>
|
||||
|
||||
<div className="video-overlay__actions">
|
||||
<div className="section__actions--centered">
|
||||
<Button label={__('Explore')} button="primary" href={lbrytvLink} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!currentlyFloating && showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
|
||||
<Suspense fallback={<div />}>{this.renderViewer()}</Suspense>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import * as SETTINGS from 'constants/settings';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeSelectFileInfoForUri, makeSelectTitleForUri } from 'lbry-redux';
|
||||
import { doClaimEligiblePurchaseRewards } from 'lbryinc';
|
||||
import {
|
||||
makeSelectIsPlaying,
|
||||
makeSelectIsPlayerFloating,
|
||||
selectPlayingUri,
|
||||
makeSelectFileRenderModeForUri,
|
||||
makeSelectStreamingUrlForUriWebProxy,
|
||||
|
@ -11,7 +11,6 @@ import {
|
|||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doAnalyticsView } from 'redux/actions/app';
|
||||
import FileRenderFloating from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
|
@ -21,6 +20,7 @@ const select = (state, props) => {
|
|||
title: makeSelectTitleForUri(uri)(state),
|
||||
fileInfo: makeSelectFileInfoForUri(uri)(state),
|
||||
isPlaying: makeSelectIsPlaying(uri)(state),
|
||||
isFloating: makeSelectIsPlayerFloating(props.location)(state),
|
||||
streamingUrl: makeSelectStreamingUrlForUriWebProxy(uri)(state),
|
||||
floatingPlayerEnabled: makeSelectClientSetting(SETTINGS.FLOATING_PLAYER)(state),
|
||||
renderMode: makeSelectFileRenderModeForUri(uri)(state),
|
||||
|
@ -29,8 +29,6 @@ const select = (state, props) => {
|
|||
|
||||
const perform = dispatch => ({
|
||||
clearPlayingUri: () => dispatch(doSetPlayingUri(null)),
|
||||
triggerAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(FileRenderFloating));
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import * as RENDER_MODES from 'constants/file_render_modes';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import Button from 'component/button';
|
||||
import classnames from 'classnames';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import FileRender from 'component/fileRender';
|
||||
import UriIndicator from 'component/uriIndicator';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import { FILE_WRAPPER_CLASS } from 'page/file/view';
|
||||
import Draggable from 'react-draggable';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
|
@ -18,50 +17,42 @@ import useIsMobile from 'effects/use-is-mobile';
|
|||
type Props = {
|
||||
isLoading: boolean,
|
||||
isPlaying: boolean,
|
||||
isFloating: boolean,
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
streamingUrl?: string,
|
||||
floatingPlayer: boolean,
|
||||
pageUri: ?string,
|
||||
title: ?string,
|
||||
floatingPlayerEnabled: boolean,
|
||||
clearPlayingUri: () => void,
|
||||
triggerAnalyticsView: (string, number) => Promise<any>,
|
||||
renderMode: string,
|
||||
claimRewards: () => void,
|
||||
};
|
||||
|
||||
export default function FloatingViewer(props: Props) {
|
||||
export default function FileRenderFloating(props: Props) {
|
||||
const {
|
||||
isPlaying,
|
||||
fileInfo,
|
||||
uri,
|
||||
streamingUrl,
|
||||
pageUri,
|
||||
title,
|
||||
isFloating,
|
||||
clearPlayingUri,
|
||||
floatingPlayerEnabled,
|
||||
triggerAnalyticsView,
|
||||
claimRewards,
|
||||
renderMode,
|
||||
} = props;
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
const [playTime, setPlayTime] = useState();
|
||||
const [fileViewerRect, setFileViewerRect] = usePersistedState('inline-file-viewer:rect');
|
||||
const [position, setPosition] = usePersistedState('floating-file-viewer:position', {
|
||||
x: -25,
|
||||
y: window.innerHeight - 400,
|
||||
});
|
||||
const inline = pageUri === uri;
|
||||
|
||||
const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode);
|
||||
const isReadyToPlay = isPlayable && (streamingUrl || (fileInfo && fileInfo.completed));
|
||||
const loadingMessage =
|
||||
fileInfo && fileInfo.blobs_completed >= 1 && (!fileInfo.download_path || !fileInfo.written_bytes)
|
||||
? __("It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.")
|
||||
: __('Loading');
|
||||
const previousUri = usePrevious(uri);
|
||||
const isNewView = uri && previousUri !== uri && isPlaying;
|
||||
const [hasRecordedView, setHasRecordedView] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
function handleResize() {
|
||||
|
@ -82,26 +73,9 @@ export default function FloatingViewer(props: Props) {
|
|||
window.removeEventListener('resize', handleResize);
|
||||
onFullscreenChange(window, 'remove', handleResize);
|
||||
};
|
||||
}, [setFileViewerRect, inline]);
|
||||
}, [setFileViewerRect, isFloating]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewView) {
|
||||
setPlayTime(Date.now());
|
||||
}
|
||||
}, [isNewView, uri]);
|
||||
|
||||
useEffect(() => {
|
||||
if (playTime && isReadyToPlay && !hasRecordedView) {
|
||||
const timeToStart = Date.now() - playTime;
|
||||
triggerAnalyticsView(uri, timeToStart).then(() => {
|
||||
claimRewards();
|
||||
setHasRecordedView(false); // This is a terrible variable name, rename this
|
||||
setPlayTime(null);
|
||||
});
|
||||
}
|
||||
}, [setPlayTime, triggerAnalyticsView, isReadyToPlay, hasRecordedView, playTime, uri, claimRewards]);
|
||||
|
||||
if (!isPlayable || !isPlaying || !uri || (!inline && (isMobile || !floatingPlayerEnabled))) {
|
||||
if (!isPlayable || !isPlaying || !uri || (isFloating && (isMobile || !floatingPlayerEnabled))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -119,29 +93,29 @@ export default function FloatingViewer(props: Props) {
|
|||
<Draggable
|
||||
onDrag={handleDrag}
|
||||
defaultPosition={position}
|
||||
position={inline ? { x: 0, y: 0 } : position}
|
||||
position={isFloating ? position : { x: 0, y: 0 }}
|
||||
bounds="parent"
|
||||
disabled={inline}
|
||||
disabled={!isFloating}
|
||||
handle=".draggable"
|
||||
cancel=".button"
|
||||
>
|
||||
<div
|
||||
className={classnames('content__viewer', {
|
||||
'content__viewer--floating': !inline,
|
||||
'content__viewer--inline': inline,
|
||||
'content__viewer--floating': isFloating,
|
||||
'content__viewer--inline': !isFloating,
|
||||
})}
|
||||
style={
|
||||
inline && fileViewerRect
|
||||
!isFloating && fileViewerRect
|
||||
? { width: fileViewerRect.width, height: fileViewerRect.height, left: fileViewerRect.x }
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={classnames('content__wrapper', {
|
||||
'content__wrapper--floating': !inline,
|
||||
'content__wrapper--floating': isFloating,
|
||||
})}
|
||||
>
|
||||
{!inline && (
|
||||
{isFloating && (
|
||||
<div className="draggable content__floating-header">
|
||||
<span className="media__uri--inline">{uri}</span>
|
||||
<div className="content__actions">
|
||||
|
@ -155,12 +129,8 @@ export default function FloatingViewer(props: Props) {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{isReadyToPlay ? (
|
||||
<FileRender currentlyFloating={!inline} uri={uri} />
|
||||
) : (
|
||||
<LoadingScreen status={loadingMessage} />
|
||||
)}
|
||||
{!inline && (
|
||||
{isReadyToPlay ? <FileRender uri={uri} /> : <LoadingScreen status={loadingMessage} />}
|
||||
{isFloating && (
|
||||
<div className="draggable content__info">
|
||||
<div className="claim-preview__title" title={title || uri}>
|
||||
{title || uri}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
// This component is entirely for triggering the start of a file view
|
||||
// The actual viewer for a file exists in TextViewer and FloatingViewer
|
||||
// The actual viewer for a file exists in TextViewer and FileRenderFloating
|
||||
// They can't exist in one component because we need to handle/listen for the start of a new file view
|
||||
// while a file is currently being viewed
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
|
@ -89,7 +89,7 @@ export default function FileRenderInitiator(props: Props) {
|
|||
if (isFree && ((autoplay && !videoOnPage && isPlayable) || RENDER_MODES.AUTO_RENDER_MODES.includes(renderMode))) {
|
||||
viewFile();
|
||||
}
|
||||
}, [autoplay, viewFile, isFree, renderMode]);
|
||||
}, [autoplay, viewFile, isFree, renderMode, isPlayable]);
|
||||
|
||||
/*
|
||||
once content is playing, let the appropriate <FileRender> take care of it...
|
||||
|
|
|
@ -35,6 +35,9 @@ export default function FileRenderInline(props: Props) {
|
|||
}, [isPlaying, setPlayTime, uri]);
|
||||
|
||||
useEffect(() => {
|
||||
/*
|
||||
note: code can currently double fire with videoViewer logic if a video is rendered by FileRenderInline (currently this never happens)
|
||||
*/
|
||||
if (playTime && isReadyToPlay) {
|
||||
const timeToStart = Date.now() - playTime;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ function AppViewer(props: Props) {
|
|||
);
|
||||
|
||||
// return (
|
||||
// <div className="file-render__viewer">
|
||||
// <div className="file-viewer">
|
||||
// {!appUrl && (
|
||||
// <LoadingScreen
|
||||
// status={loading ? __('Almost there') : __('Unable to view this file in the app')}
|
||||
|
|
|
@ -98,7 +98,7 @@ class DocumentViewer extends React.PureComponent<Props, State> {
|
|||
const errorMessage = __("Sorry, looks like we can't load the document.");
|
||||
|
||||
return (
|
||||
<div className="file-render__viewer file-render__viewer--document">
|
||||
<div className="file-viewer file-viewer--document">
|
||||
{loading && !error && <div className="placeholder--text-document" />}
|
||||
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
|
||||
{isReady && this.renderDocument()}
|
||||
|
|
|
@ -58,7 +58,7 @@ class DocxViewer extends React.PureComponent<Props, State> {
|
|||
const errorMessage = __("Sorry, looks like we can't load the document.");
|
||||
|
||||
return (
|
||||
<div className="file-render__viewer file-render__viewer--document">
|
||||
<div className="file-viewer file-viewer--document">
|
||||
{loading && <LoadingScreen status={loadingMessage} spinner />}
|
||||
{error && <LoadingScreen status={errorMessage} spinner={false} />}
|
||||
{content && <div className="file-render__content" dangerouslySetInnerHTML={{ __html: content }} />}
|
||||
|
|
|
@ -35,10 +35,7 @@ class HtmlViewer extends React.PureComponent<Props, State> {
|
|||
const { source } = this.props;
|
||||
const { loading } = this.state;
|
||||
return (
|
||||
<div
|
||||
className="file-render__viewer file-render__viewer--html file-render__viewer--iframe"
|
||||
onContextMenu={stopContextMenu}
|
||||
>
|
||||
<div className="file-viewer file-viewer--html file-viewer--iframe" onContextMenu={stopContextMenu}>
|
||||
{loading && <div className="placeholder--text-document" />}
|
||||
{/* @if TARGET='app' */}
|
||||
<iframe ref={this.iframe} hidden={loading} sandbox="" title={__('File preview')} src={`file://${source}`} />
|
||||
|
|
|
@ -20,7 +20,7 @@ function ImageViewer(props: Props) {
|
|||
/>
|
||||
)}
|
||||
{!loadingError && (
|
||||
<div className="file-render__viewer">
|
||||
<div className="file-viewer">
|
||||
<img src={source} onError={() => setLoadingError(true)} />
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -12,8 +12,8 @@ class PdfViewer extends React.PureComponent<Props> {
|
|||
const { source } = this.props;
|
||||
const src = IS_WEB ? source : `file://${source}`;
|
||||
return (
|
||||
<div className="file-render__viewer file-render__viewer--document" onContextMenu={stopContextMenu}>
|
||||
<div className="file-render__viewer file-render__viewer--iframe">
|
||||
<div className="file-viewer file-viewer--document" onContextMenu={stopContextMenu}>
|
||||
<div className="file-viewer file-viewer--iframe">
|
||||
<IframeReact title={__('File preview')} src={src} />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectFileInfoForUri, makeSelectThumbnailForUri } from 'lbry-redux';
|
||||
import { doChangeVolume, doChangeMute } from 'redux/actions/app';
|
||||
import { doChangeVolume, doChangeMute, doAnalyticsView } from 'redux/actions/app';
|
||||
import { selectVolume, selectMute } from 'redux/selectors/app';
|
||||
import { savePosition, doSetPlayingUri } from 'redux/actions/content';
|
||||
import VideoViewer from './view';
|
||||
import { withRouter } from 'react-router';
|
||||
import { doClaimEligiblePurchaseRewards } from 'lbryinc';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import * as SETTINGS from 'constants/settings';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { search } = props.location;
|
||||
|
@ -13,7 +16,8 @@ const select = (state, props) => {
|
|||
const position = urlParams.get('t');
|
||||
|
||||
return {
|
||||
autoplayParam: autoplay,
|
||||
autoplayIfEmbedded: Boolean(autoplay),
|
||||
autoplaySetting: Boolean(makeSelectClientSetting(SETTINGS.AUTOPLAY)(state)),
|
||||
volume: selectVolume(state),
|
||||
muted: selectMute(state),
|
||||
position: position,
|
||||
|
@ -28,6 +32,8 @@ const perform = dispatch => ({
|
|||
savePosition: (uri, position) => dispatch(savePosition(uri, position)),
|
||||
changeMute: muted => dispatch(doChangeMute(muted)),
|
||||
setPlayingUri: uri => dispatch(doSetPlayingUri(uri)),
|
||||
doAnalyticsView: (uri, timeToStart) => dispatch(doAnalyticsView(uri, timeToStart)),
|
||||
claimRewards: () => dispatch(doClaimEligiblePurchaseRewards()),
|
||||
});
|
||||
|
||||
export default withRouter(connect(select, perform)(VideoViewer));
|
||||
|
|
|
@ -7,7 +7,12 @@ import eventTracking from 'videojs-event-tracking';
|
|||
import isUserTyping from 'util/detect-typing';
|
||||
import analytics from 'analytics';
|
||||
import { EmbedContext } from 'page/embedWrapper/view';
|
||||
import classnames from 'classnames';
|
||||
import { FORCE_CONTENT_TYPE_PLAYER } from 'constants/claim';
|
||||
import AutoplayCountdown from 'component/autoplayCountdown';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
import FileViewerEmbeddedEnded from 'lbrytv/component/fileViewerEmbeddedEnded';
|
||||
import FileViewerEmbeddedTitle from 'lbrytv/component/fileViewerEmbeddedTitle';
|
||||
|
||||
const F11_KEYCODE = 122;
|
||||
const SPACE_BAR_KEYCODE = 32;
|
||||
|
@ -59,17 +64,17 @@ type Props = {
|
|||
thumbnail: string,
|
||||
hasFileInfo: boolean,
|
||||
claim: Claim,
|
||||
autoplayParam: ?boolean,
|
||||
onStartedCb: () => void,
|
||||
onEndedCb: () => void,
|
||||
uri: string,
|
||||
autoplaySetting: boolean,
|
||||
autoplayIfEmbedded: boolean,
|
||||
doAnalyticsView: (string, number) => Promise<any>,
|
||||
claimRewards: () => void,
|
||||
};
|
||||
|
||||
function VideoViewer(props: Props) {
|
||||
const {
|
||||
contentType,
|
||||
source,
|
||||
onEndedCb,
|
||||
onStartedCb,
|
||||
changeVolume,
|
||||
changeMute,
|
||||
volume,
|
||||
|
@ -77,23 +82,31 @@ function VideoViewer(props: Props) {
|
|||
thumbnail,
|
||||
position,
|
||||
claim,
|
||||
autoplayParam,
|
||||
uri,
|
||||
autoplaySetting,
|
||||
autoplayIfEmbedded,
|
||||
doAnalyticsView,
|
||||
claimRewards,
|
||||
} = props;
|
||||
const claimId = claim && claim.claim_id;
|
||||
const videoRef = useRef();
|
||||
const isAudio = contentType.includes('audio');
|
||||
const embedded = useContext(EmbedContext);
|
||||
|
||||
if (embedded && !autoplayParam) {
|
||||
VIDEO_JS_OPTIONS.autoplay = false;
|
||||
}
|
||||
|
||||
if (autoplayParam) {
|
||||
VIDEO_JS_OPTIONS.muted = true;
|
||||
if (embedded) {
|
||||
VIDEO_JS_OPTIONS.autoplay = autoplayIfEmbedded;
|
||||
VIDEO_JS_OPTIONS.muted = autoplayIfEmbedded;
|
||||
} else if (autoplaySetting) {
|
||||
VIDEO_JS_OPTIONS.autoplay = autoplaySetting;
|
||||
}
|
||||
|
||||
const forcePlayer = FORCE_CONTENT_TYPE_PLAYER.includes(contentType);
|
||||
const [requireRedraw, setRequireRedraw] = useState(false);
|
||||
const [isPlaying, setIsPlaying] = useState(false);
|
||||
const [showAutoplayCountdown, setShowAutoplayCountdown] = useState(false);
|
||||
const [isEndededEmbed, setIsEndededEmbed] = useState(false);
|
||||
const previousUri = usePrevious(uri);
|
||||
|
||||
let player;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -110,14 +123,14 @@ function VideoViewer(props: Props) {
|
|||
};
|
||||
|
||||
// thumb looks bad in app, and if autoplay, flashing poster is annoying
|
||||
if (isAudio || (embedded && !autoplayParam)) {
|
||||
if (isAudio || (embedded && !autoplayIfEmbedded)) {
|
||||
videoJsOptions.poster = thumbnail;
|
||||
}
|
||||
|
||||
if (!requireRedraw) {
|
||||
player = videojs(videoNode, videoJsOptions, function() {
|
||||
if (!autoplayParam) player.volume(volume);
|
||||
player.muted(autoplayParam || muted);
|
||||
if (!autoplayIfEmbedded) player.volume(volume);
|
||||
player.muted(autoplayIfEmbedded || muted);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -193,13 +206,21 @@ function VideoViewer(props: Props) {
|
|||
function doTrackingBuffered(e: Event, data: any) {
|
||||
analytics.videoBufferEvent(claimId, data.currentTime);
|
||||
}
|
||||
|
||||
function doTrackingFirstPlay(e: Event, data: any) {
|
||||
analytics.videoStartEvent(claimId, data.secondsToLoad);
|
||||
onStartedCb();
|
||||
|
||||
doAnalyticsView(uri, data.secondsToLoad).then(() => {
|
||||
claimRewards();
|
||||
});
|
||||
}
|
||||
|
||||
function doEnded() {
|
||||
onEndedCb();
|
||||
if (embedded) {
|
||||
setIsEndededEmbed(true);
|
||||
} else if (autoplaySetting) {
|
||||
setShowAutoplayCountdown(true);
|
||||
}
|
||||
}
|
||||
|
||||
function doVolume(e: Event) {
|
||||
|
@ -209,11 +230,25 @@ function VideoViewer(props: Props) {
|
|||
changeMute(isMuted);
|
||||
}
|
||||
|
||||
function doPlay() {
|
||||
setIsPlaying(true);
|
||||
setShowAutoplayCountdown(false);
|
||||
setIsEndededEmbed(false);
|
||||
}
|
||||
|
||||
if (player) {
|
||||
player.on('tracking:buffered', doTrackingBuffered);
|
||||
|
||||
player.on('tracking:firstplay', doTrackingFirstPlay);
|
||||
// FIXME: above is not firing on subsequent renders (though the effect fires), maybe below check can reset?
|
||||
if (uri && previousUri !== uri) {
|
||||
// do reset?
|
||||
}
|
||||
|
||||
player.on('ended', doEnded);
|
||||
player.on('volumechange', doVolume);
|
||||
player.on('play', doPlay);
|
||||
player.on('pause', () => setIsPlaying(false));
|
||||
|
||||
// fixes #3498 (https://github.com/lbryio/lbry-desktop/issues/3498)
|
||||
// summary: on firefox the focus would stick to the fullscreen button which caused buggy behavior with spacebar
|
||||
|
@ -226,7 +261,7 @@ function VideoViewer(props: Props) {
|
|||
player.off();
|
||||
}
|
||||
};
|
||||
}, [claimId, player, changeVolume, changeMute, onEndedCb, onStartedCb]);
|
||||
}, [claimId, player, changeVolume, changeMute]); // FIXME: more dependencies?
|
||||
|
||||
useEffect(() => {
|
||||
if (player && position) {
|
||||
|
@ -235,7 +270,15 @@ function VideoViewer(props: Props) {
|
|||
}, [player, position]);
|
||||
|
||||
return (
|
||||
<div className="file-render__viewer" onContextMenu={stopContextMenu}>
|
||||
<div
|
||||
className={classnames('file-viewer', {
|
||||
'file-viewer--is-playing': isPlaying,
|
||||
})}
|
||||
onContextMenu={stopContextMenu}
|
||||
>
|
||||
{showAutoplayCountdown && <AutoplayCountdown uri={uri} />}
|
||||
{isEndededEmbed && <FileViewerEmbeddedEnded uri={uri} />}
|
||||
{embedded && !isEndededEmbed && <FileViewerEmbeddedTitle uri={uri} />}
|
||||
{!requireRedraw && (
|
||||
<div data-vjs-player>
|
||||
{isAudio ? <audio ref={videoRef} className="video-js" /> : <video ref={videoRef} className="video-js" />}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { connect } from 'react-redux';
|
||||
import EmbedWrapperPage from './view';
|
||||
import { doResolveUri, makeSelectClaimForUri, buildURI } from 'lbry-redux';
|
||||
import { doAnalyticsView } from 'redux/actions/app';
|
||||
|
||||
const select = (state, props) => {
|
||||
const { match } = props;
|
||||
|
@ -17,11 +16,7 @@ const select = (state, props) => {
|
|||
const perform = dispatch => {
|
||||
return {
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
triggerAnalyticsView: uri => dispatch(doAnalyticsView(uri)),
|
||||
};
|
||||
};
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(EmbedWrapperPage);
|
||||
export default connect(select, perform)(EmbedWrapperPage);
|
||||
|
|
|
@ -1,38 +1,28 @@
|
|||
// @flow
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import FileRender from 'component/fileRender';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
resolveUri: string => void,
|
||||
claim: Claim,
|
||||
triggerAnalyticsView: string => Promise<any>,
|
||||
};
|
||||
// $FlowFixMe apparently flow thinks this is wrong.
|
||||
export const EmbedContext = React.createContext();
|
||||
const EmbedWrapperPage = (props: Props) => {
|
||||
const { resolveUri, claim, uri, triggerAnalyticsView } = props;
|
||||
const [hasRecordedView, setHasRecordedView] = useState(false);
|
||||
|
||||
function onStartedCallback() {
|
||||
if (!hasRecordedView && uri && claim) {
|
||||
triggerAnalyticsView(uri).then(() => {
|
||||
setHasRecordedView(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
const { resolveUri, claim, uri } = props;
|
||||
|
||||
useEffect(() => {
|
||||
if (resolveUri && uri) {
|
||||
resolveUri(uri);
|
||||
}
|
||||
}, []);
|
||||
}, [resolveUri, uri]);
|
||||
|
||||
return (
|
||||
<div className={'embed__wrapper'}>
|
||||
{claim && (
|
||||
<EmbedContext.Provider value>
|
||||
<FileRender uri={uri} embedded onStartedCallback={onStartedCallback} />
|
||||
<FileRender uri={uri} embedded />
|
||||
</EmbedContext.Provider>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@ import RecommendedContent from 'component/recommendedContent';
|
|||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
|
||||
export const FILE_WRAPPER_CLASS = 'grid-area--content';
|
||||
export const FILE_WRAPPER_CLASS = 'file-page__video-container';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
|
|
|
@ -2,149 +2,79 @@ import { createSelector } from 'reselect';
|
|||
|
||||
export const selectState = state => state.app || {};
|
||||
|
||||
export const selectPlatform = createSelector(
|
||||
selectState,
|
||||
state => state.platform
|
||||
);
|
||||
export const selectPlatform = createSelector(selectState, state => state.platform);
|
||||
|
||||
export const selectUpdateUrl = createSelector(
|
||||
selectPlatform,
|
||||
platform => {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'https://lbry.com/get/lbry.dmg';
|
||||
case 'linux':
|
||||
return 'https://lbry.com/get/lbry.deb';
|
||||
case 'win32':
|
||||
return 'https://lbry.com/get/lbry.exe';
|
||||
default:
|
||||
throw Error('Unknown platform');
|
||||
}
|
||||
export const selectUpdateUrl = createSelector(selectPlatform, platform => {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return 'https://lbry.com/get/lbry.dmg';
|
||||
case 'linux':
|
||||
return 'https://lbry.com/get/lbry.deb';
|
||||
case 'win32':
|
||||
return 'https://lbry.com/get/lbry.exe';
|
||||
default:
|
||||
throw Error('Unknown platform');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const selectHasClickedComment = createSelector(
|
||||
selectState,
|
||||
state => state.hasClickedComment
|
||||
);
|
||||
export const selectHasClickedComment = createSelector(selectState, state => state.hasClickedComment);
|
||||
|
||||
export const selectRemoteVersion = createSelector(
|
||||
selectState,
|
||||
state => state.remoteVersion
|
||||
);
|
||||
export const selectRemoteVersion = createSelector(selectState, state => state.remoteVersion);
|
||||
|
||||
export const selectIsUpgradeAvailable = createSelector(
|
||||
selectState,
|
||||
state => state.isUpgradeAvailable
|
||||
);
|
||||
export const selectIsUpgradeAvailable = createSelector(selectState, state => state.isUpgradeAvailable);
|
||||
|
||||
export const selectUpgradeFilename = createSelector(
|
||||
selectPlatform,
|
||||
selectRemoteVersion,
|
||||
(platform, version) => {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return `LBRY_${version}.dmg`;
|
||||
case 'linux':
|
||||
return `LBRY_${version}.deb`;
|
||||
case 'win32':
|
||||
return `LBRY_${version}.exe`;
|
||||
default:
|
||||
throw Error('Unknown platform');
|
||||
}
|
||||
export const selectUpgradeFilename = createSelector(selectPlatform, selectRemoteVersion, (platform, version) => {
|
||||
switch (platform) {
|
||||
case 'darwin':
|
||||
return `LBRY_${version}.dmg`;
|
||||
case 'linux':
|
||||
return `LBRY_${version}.deb`;
|
||||
case 'win32':
|
||||
return `LBRY_${version}.exe`;
|
||||
default:
|
||||
throw Error('Unknown platform');
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const selectDownloadProgress = createSelector(
|
||||
selectState,
|
||||
state => state.downloadProgress
|
||||
);
|
||||
export const selectDownloadProgress = createSelector(selectState, state => state.downloadProgress);
|
||||
|
||||
export const selectDownloadComplete = createSelector(
|
||||
selectState,
|
||||
state => state.upgradeDownloadCompleted
|
||||
);
|
||||
export const selectDownloadComplete = createSelector(selectState, state => state.upgradeDownloadCompleted);
|
||||
|
||||
export const selectIsUpgradeSkipped = createSelector(
|
||||
selectState,
|
||||
state => state.isUpgradeSkipped
|
||||
);
|
||||
export const selectIsUpgradeSkipped = createSelector(selectState, state => state.isUpgradeSkipped);
|
||||
|
||||
export const selectUpgradeDownloadPath = createSelector(
|
||||
selectState,
|
||||
state => state.downloadPath
|
||||
);
|
||||
export const selectUpgradeDownloadPath = createSelector(selectState, state => state.downloadPath);
|
||||
|
||||
export const selectUpgradeDownloadItem = createSelector(
|
||||
selectState,
|
||||
state => state.downloadItem
|
||||
);
|
||||
export const selectUpgradeDownloadItem = createSelector(selectState, state => state.downloadItem);
|
||||
|
||||
export const selectAutoUpdateDownloaded = createSelector(
|
||||
selectState,
|
||||
state => state.autoUpdateDownloaded
|
||||
);
|
||||
export const selectAutoUpdateDownloaded = createSelector(selectState, state => state.autoUpdateDownloaded);
|
||||
|
||||
export const selectAutoUpdateDeclined = createSelector(
|
||||
selectState,
|
||||
state => state.autoUpdateDeclined
|
||||
);
|
||||
export const selectAutoUpdateDeclined = createSelector(selectState, state => state.autoUpdateDeclined);
|
||||
|
||||
export const selectDaemonVersionMatched = createSelector(
|
||||
selectState,
|
||||
state => state.daemonVersionMatched
|
||||
);
|
||||
export const selectDaemonVersionMatched = createSelector(selectState, state => state.daemonVersionMatched);
|
||||
|
||||
export const selectVolume = createSelector(
|
||||
selectState,
|
||||
state => state.volume
|
||||
);
|
||||
export const selectVolume = createSelector(selectState, state => state.volume);
|
||||
|
||||
export const selectMute = createSelector(
|
||||
selectState,
|
||||
state => state.muted
|
||||
);
|
||||
export const selectMute = createSelector(selectState, state => state.muted);
|
||||
|
||||
export const selectUpgradeTimer = createSelector(
|
||||
selectState,
|
||||
state => state.checkUpgradeTimer
|
||||
);
|
||||
export const selectUpgradeTimer = createSelector(selectState, state => state.checkUpgradeTimer);
|
||||
|
||||
export const selectModal = createSelector(
|
||||
selectState,
|
||||
state => {
|
||||
if (!state.modal) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: state.modal,
|
||||
modalProps: state.modalProps,
|
||||
};
|
||||
export const selectModal = createSelector(selectState, state => {
|
||||
if (!state.modal) {
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
export const selectSearchOptionsExpanded = createSelector(
|
||||
selectState,
|
||||
state => state.searchOptionsExpanded
|
||||
);
|
||||
return {
|
||||
id: state.modal,
|
||||
modalProps: state.modalProps,
|
||||
};
|
||||
});
|
||||
|
||||
export const selectWelcomeVersion = createSelector(
|
||||
selectState,
|
||||
state => state.welcomeVersion
|
||||
);
|
||||
export const selectSearchOptionsExpanded = createSelector(selectState, state => state.searchOptionsExpanded);
|
||||
|
||||
export const selectAllowAnalytics = createSelector(
|
||||
selectState,
|
||||
state => state.allowAnalytics
|
||||
);
|
||||
export const selectWelcomeVersion = createSelector(selectState, state => state.welcomeVersion);
|
||||
|
||||
export const selectScrollStartingPosition = createSelector(
|
||||
selectState,
|
||||
state => state.currentScroll
|
||||
);
|
||||
export const selectAllowAnalytics = createSelector(selectState, state => state.allowAnalytics);
|
||||
|
||||
export const selectIsPasswordSaved = createSelector(
|
||||
selectState,
|
||||
state => state.isPasswordSaved
|
||||
);
|
||||
export const selectScrollStartingPosition = createSelector(selectState, state => state.currentScroll);
|
||||
|
||||
export const selectIsPasswordSaved = createSelector(selectState, state => state.isPasswordSaved);
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
parseURI,
|
||||
makeSelectContentTypeForUri,
|
||||
makeSelectFileNameForUri,
|
||||
buildURI,
|
||||
} from 'lbry-redux';
|
||||
import { selectAllCostInfoByUri, makeSelectCostInfoForUri } from 'lbryinc';
|
||||
import { selectShowMatureContent } from 'redux/selectors/settings';
|
||||
|
@ -33,6 +34,17 @@ export const selectPlayingUri = createSelector(selectState, state => state.playi
|
|||
|
||||
export const makeSelectIsPlaying = (uri: string) => createSelector(selectPlayingUri, playingUri => playingUri === uri);
|
||||
|
||||
// below is dumb, some context: https://stackoverflow.com/questions/39622864/access-react-router-state-in-selector
|
||||
export const makeSelectIsPlayerFloating = location =>
|
||||
createSelector(selectPlayingUri, playingUri => {
|
||||
try {
|
||||
const { pathname, hash } = location;
|
||||
const newpath = buildURI(parseURI(pathname.slice(1).replace(/:/g, '#')));
|
||||
return playingUri && playingUri !== newpath + hash;
|
||||
} catch (e) {}
|
||||
return !!playingUri;
|
||||
});
|
||||
|
||||
export const makeSelectContentPositionForUri = (uri: string) =>
|
||||
createSelector(selectState, makeSelectClaimForUri(uri), (state, claim) => {
|
||||
if (!claim) {
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
|
||||
.content__actions,
|
||||
.content__floating-header {
|
||||
z-index: 1;
|
||||
z-index: 3; /*must be one higher than .file-viewer__overlay */
|
||||
}
|
||||
|
||||
.content__info {
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
.file-page {
|
||||
.grid-area--content + .card,
|
||||
.file-page__video-container + .card,
|
||||
.file-render + .card,
|
||||
.content__cover + .card,
|
||||
.card + .file-render,
|
||||
.card + .grid-area--content,
|
||||
.card + .file-page__video-container,
|
||||
.card + .content__cover {
|
||||
margin-top: var(--spacing-large);
|
||||
}
|
||||
|
@ -38,6 +38,13 @@
|
|||
z-index: 1;
|
||||
overflow: hidden;
|
||||
max-height: var(--inline-player-max-height);
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
.file-render--embed {
|
||||
border-radius: 0;
|
||||
position: fixed;
|
||||
max-height: none;
|
||||
}
|
||||
|
||||
.file-render--document {
|
||||
|
@ -76,7 +83,7 @@
|
|||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.file-render__viewer {
|
||||
.file-viewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
@ -108,7 +115,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-render__viewer--iframe {
|
||||
.file-viewer--iframe {
|
||||
display: flex; /*this eliminates extra height from whitespace, if someone edits this with a better technique, tell Jeremy*/
|
||||
/*
|
||||
ideally iframes would dynamiclly grow, see <IframeReact> for a start at this
|
||||
|
@ -129,6 +136,116 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-viewer__overlay {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 2;
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-body); /* put back font size from videojs change*/
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-large);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
font-size: var(--font-small);
|
||||
}
|
||||
|
||||
.button--uri-indicator,
|
||||
.button--link {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
.content__viewer--floating {
|
||||
.file-viewer__overlay {
|
||||
padding: var(--spacing-medium);
|
||||
}
|
||||
|
||||
.file-viewer__overlay-title,
|
||||
.file-viewer__overlay-secondary {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
@media (max-width: $breakpoint-small) {
|
||||
.file-viewer__overlay {
|
||||
padding: var(--spacing-medium);
|
||||
}
|
||||
|
||||
.file-viewer__overlay-title,
|
||||
.file-viewer__overlay-secondary {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.file-viewer__overlay-title {
|
||||
text-align: center;
|
||||
font-size: var(--font-large);
|
||||
margin-bottom: var(--spacing-medium);
|
||||
}
|
||||
.file-viewer__overlay-secondary {
|
||||
color: var(--color-text-subtitle);
|
||||
margin-bottom: var(--spacing-medium);
|
||||
}
|
||||
.file-viewer__overlay-actions {
|
||||
.button + .button {
|
||||
margin-left: var(--spacing-medium);
|
||||
}
|
||||
}
|
||||
|
||||
.file-viewer__overlay-logo {
|
||||
color: var(--color-white);
|
||||
font-weight: bold;
|
||||
|
||||
.icon {
|
||||
height: 30px;
|
||||
stroke-width: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.file-viewer--is-playing:not(:hover) .file-viewer__embedded-title {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.file-viewer__embedded-title {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
left: auto;
|
||||
top: 0;
|
||||
line-height: 1.5;
|
||||
z-index: 2;
|
||||
color: var(--color-white);
|
||||
font-size: var(--font-large);
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
padding: var(--spacing-small);
|
||||
background-repeat: repeat-x;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
|
||||
-moz-transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
-webkit-transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
transition: opacity 0.25s cubic-bezier(0, 0, 0.2, 1);
|
||||
.button {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
.file-viewer__embedded-title-logo {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.file-render__content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -212,49 +329,30 @@
|
|||
}
|
||||
|
||||
.file-render {
|
||||
border-radius: var(--card-radius);
|
||||
|
||||
.video-js {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.vjs-big-play-button {
|
||||
@extend .button--icon;
|
||||
@extend .button--play;
|
||||
border: none;
|
||||
position: static;
|
||||
z-index: 2;
|
||||
.vjs-big-play-button {
|
||||
@extend .button--icon;
|
||||
@extend .button--play;
|
||||
border: none;
|
||||
position: static;
|
||||
z-index: 2;
|
||||
|
||||
.vjs-icon-placeholder {
|
||||
display: none;
|
||||
}
|
||||
.vjs-icon-placeholder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-render__embed {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
.file-render--embed {
|
||||
/* on embeds, do not inject our colors until interaction*/
|
||||
|
||||
.video-js {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.vjs-big-play-button {
|
||||
@extend .button--icon;
|
||||
@extend .button--play;
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
border: none;
|
||||
position: static;
|
||||
z-index: 2;
|
||||
|
||||
.vjs-icon-placeholder {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.vjs-big-play-button {
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
|
||||
&.vjs-paused {
|
||||
.vjs-big-play-button {
|
||||
|
@ -270,67 +368,55 @@
|
|||
}
|
||||
}
|
||||
|
||||
.video-overlay__wrapper {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
z-index: 999;
|
||||
color: var(--color-white);
|
||||
.autoplay-countdown {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--spacing-large);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button--uri-indicator {
|
||||
color: var(--color-gray-3);
|
||||
}
|
||||
.autoplay-countdown__timer {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: var(--font-small);
|
||||
}
|
||||
.autoplay-countdown__counter {
|
||||
margin-top: var(--spacing-medium);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
align-items: flex-start;
|
||||
padding: var(--spacing-small);
|
||||
|
||||
.button,
|
||||
.video-overlay__subtitle,
|
||||
.video-overlay__actions {
|
||||
font-size: var(--font-small);
|
||||
.autoplay-countdown__button {
|
||||
/* Border size and color */
|
||||
border: 3px solid transparent;
|
||||
/* Creates a circle */
|
||||
border-radius: 50%;
|
||||
/* Circle size */
|
||||
display: inline-block;
|
||||
height: 86px;
|
||||
width: 86px;
|
||||
/* Use transform to rotate to adjust where opening appears */
|
||||
transition: border 1s;
|
||||
transform: rotate(45deg);
|
||||
.button {
|
||||
background-color: transparent;
|
||||
transform: rotate(-45deg);
|
||||
&:hover {
|
||||
background-color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-overlay__title {
|
||||
font-size: var(--font-title);
|
||||
font-weight: var(--font-weight-light);
|
||||
margin-top: var(--spacing-medium);
|
||||
margin-bottom: var(--spacing-small);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin: 0;
|
||||
font-size: var(--font-medium);
|
||||
}
|
||||
.autoplay-countdown__button--4 {
|
||||
border-top-color: #fff;
|
||||
}
|
||||
|
||||
.video-overlay__subtitle {
|
||||
color: var(--color-gray-3);
|
||||
margin: var(--spacing-medium) 0;
|
||||
line-height: 1;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin: 0;
|
||||
}
|
||||
.autoplay-countdown__button--3 {
|
||||
border-top-color: #fff;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
.video-overlay__actions {
|
||||
margin-top: var(--spacing-large);
|
||||
|
||||
.button--link {
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin-top: var(--spacing-small);
|
||||
}
|
||||
.autoplay-countdown__button--2 {
|
||||
border-top-color: #fff;
|
||||
border-right-color: #fff;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.autoplay-countdown__button--1 {
|
||||
border-color: #fff;
|
||||
}
|
||||
|
|
84
yarn.lock
84
yarn.lock
|
@ -758,6 +758,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.5.5":
|
||||
version "7.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.2.tgz#d90df0583a3a252f09aaa619665367bae518db06"
|
||||
integrity sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.8.3", "@babel/template@^7.8.6":
|
||||
version "7.8.6"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
|
||||
|
@ -1171,6 +1178,37 @@
|
|||
url-toolkit "^2.1.3"
|
||||
video.js "^6.8.0 || ^7.0.0"
|
||||
|
||||
"@videojs/http-streaming@1.12.2":
|
||||
version "1.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-1.12.2.tgz#9ee790ff5e83eb3aab08ba3c8e434ace92a16013"
|
||||
integrity sha512-/YoApr0Ihaqo2eOLSQA3AdUDgor3VPeRAc+mCFvEB8265OUHxhIBUQ+JoGEKYk6PY2XLK1JqNC0lDuqpr61TPQ==
|
||||
dependencies:
|
||||
aes-decrypter "3.0.0"
|
||||
global "^4.3.0"
|
||||
m3u8-parser "4.4.0"
|
||||
mpd-parser "0.10.0"
|
||||
mux.js "5.5.1"
|
||||
url-toolkit "^2.1.3"
|
||||
video.js "^6.8.0 || ^7.0.0"
|
||||
|
||||
"@videojs/vhs-utils@^1.1.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@videojs/vhs-utils/-/vhs-utils-1.3.0.tgz#04fe402f603af9a5df4b88881fabba0cf13814c2"
|
||||
integrity sha512-oiqXDtHQqDPun7JseWkirUHGrgdYdeF12goUut5z7vwAj4DmUufEPFJ4xK5hYGXGFDyDhk2rSFOR122Ze6qXyQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
global "^4.3.2"
|
||||
url-toolkit "^2.1.6"
|
||||
|
||||
"@videojs/xhr@2.5.1":
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@videojs/xhr/-/xhr-2.5.1.tgz#26bc5a79dbb3b03bfb13742c6ce559f89e90719e"
|
||||
integrity sha512-wV9nGESHseSK+S9ePEru2+OJZ1jq/ZbbzniGQ4weAmTIepuBMSYPx5zrxxQA0E786T5ykpO8ts+LayV+3/oI2w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
global "~4.4.0"
|
||||
is-function "^1.0.1"
|
||||
|
||||
"@webassemblyjs/ast@1.8.5":
|
||||
version "1.8.5"
|
||||
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
|
||||
|
@ -4795,7 +4833,7 @@ global@4.3.2, global@~4.3.0:
|
|||
min-document "^2.19.0"
|
||||
process "~0.5.1"
|
||||
|
||||
global@^4.3.0, global@^4.3.1, global@^4.3.2:
|
||||
global@^4.3.0, global@^4.3.1, global@^4.3.2, global@~4.4.0:
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406"
|
||||
dependencies:
|
||||
|
@ -6855,6 +6893,16 @@ move-concurrently@^1.0.1:
|
|||
rimraf "^2.5.4"
|
||||
run-queue "^1.0.3"
|
||||
|
||||
mpd-parser@0.10.0:
|
||||
version "0.10.0"
|
||||
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.10.0.tgz#e48a39a4ecd3b5bbd0ed4ac5991b9cc36bcd9599"
|
||||
integrity sha512-eIqkH/2osPr7tIIjhRmDWqm2wdJ7Q8oPfWvdjealzsLV2D2oNe0a0ae2gyYYs1sw5e5hdssDA2V6Sz8MW+Uvvw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
"@videojs/vhs-utils" "^1.1.0"
|
||||
global "^4.3.2"
|
||||
xmldom "^0.1.27"
|
||||
|
||||
mpd-parser@0.8.1:
|
||||
version "0.8.1"
|
||||
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.8.1.tgz#db299dbec337999fbbbace989d227c7b03dc8ea7"
|
||||
|
@ -6893,6 +6941,11 @@ mux.js@5.2.1:
|
|||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-5.2.1.tgz#6698761fc88da5acecea0758ac25f11d3a08bee8"
|
||||
|
||||
mux.js@5.5.1:
|
||||
version "5.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-5.5.1.tgz#5bd5d7b2e5e5560da8126928e93af3c532e08372"
|
||||
integrity sha512-5VmmjADBqS4++8pTI6poSRJ+chHdaoI4XErcQPM5w4QfwaDl+FQlSI0iOgWbYDn6CBCbDRKaSCcEiN2K5aHNGQ==
|
||||
|
||||
nan@^2.12.1, nan@^2.13.2:
|
||||
version "2.14.0"
|
||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||
|
@ -10737,7 +10790,7 @@ url-to-options@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
|
||||
|
||||
url-toolkit@^2.1.1, url-toolkit@^2.1.3:
|
||||
url-toolkit@^2.1.1, url-toolkit@^2.1.3, url-toolkit@^2.1.6:
|
||||
version "2.1.6"
|
||||
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
|
||||
|
||||
|
@ -10850,6 +10903,20 @@ vfile@^2.0.0:
|
|||
unist-util-stringify-position "^1.0.0"
|
||||
vfile-message "^1.0.0"
|
||||
|
||||
"video.js@^6 || ^7":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.7.5.tgz#7b066c4eb56fdf4980d3be09491ce520ffe8ed75"
|
||||
integrity sha512-+HSp2KNZmGzkmTecXyaXxEGi3F41WAm43PqNp3hWq5wYUQOHwcRu5YhhCz+5q0fDV+SlnFMSSLl/I6QLMlYv/g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.5.5"
|
||||
"@videojs/http-streaming" "1.12.2"
|
||||
"@videojs/xhr" "2.5.1"
|
||||
global "4.3.2"
|
||||
keycode "^2.2.0"
|
||||
safe-json-parse "4.0.0"
|
||||
videojs-font "3.2.0"
|
||||
videojs-vtt.js "^0.15.2"
|
||||
|
||||
"video.js@^6.8.0 || ^7.0.0", video.js@^7.0.0, video.js@^7.2.2:
|
||||
version "7.6.6"
|
||||
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.6.6.tgz#e7c9163d53f9b0e05ccb5ac0f79d02fa49b4d3ac"
|
||||
|
@ -10874,6 +10941,14 @@ videojs-font@3.2.0:
|
|||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.2.0.tgz#212c9d3f4e4ec3fa7345167d64316add35e92232"
|
||||
|
||||
videojs-logo@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/videojs-logo/-/videojs-logo-2.0.0.tgz#2d954100b540060309853559b9b6d59c519057f7"
|
||||
integrity sha512-5l/i6N2Yz41j03Rxh/IHSANjryR/HeftLeBv9qGJacabKgSKzA8tAath44XZm/HMED2lrgCp0msDV1RLynlljQ==
|
||||
dependencies:
|
||||
global "^4.3.2"
|
||||
video.js "^6 || ^7"
|
||||
|
||||
videojs-vtt.js@^0.14.1:
|
||||
version "0.14.1"
|
||||
resolved "https://registry.yarnpkg.com/videojs-vtt.js/-/videojs-vtt.js-0.14.1.tgz#da583eb1fc9c81c826a9432b706040e8dea49911"
|
||||
|
@ -11239,6 +11314,11 @@ xmlbuilder@^10.0.0:
|
|||
version "10.1.1"
|
||||
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"
|
||||
|
||||
xmldom@^0.1.27:
|
||||
version "0.1.31"
|
||||
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
|
||||
integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
|
||||
|
||||
xpipe@*:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/xpipe/-/xpipe-1.0.5.tgz#8dd8bf45fc3f7f55f0e054b878f43a62614dafdf"
|
||||
|
|
Loading…
Reference in a new issue