semi-broken

This commit is contained in:
Jeremy Kauffman 2020-04-13 19:48:11 -04:00 committed by Sean Yesmunt
parent e97fe3db34
commit 793f622d8d
30 changed files with 574 additions and 431 deletions

View 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);

View 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);

View 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);

View 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);

View file

@ -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",

View file

@ -284,7 +284,7 @@ function App(props: Props) {
<React.Fragment>
<Router />
<ModalRouter />
<FileRenderFloating pageUri={uri} />
<FileRenderFloating />
{isEnhancedLayout && <Yrbl className="yrbl--enhanced" />}
{/* @if TARGET='app' */}

View file

@ -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);

View file

@ -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>
);

View file

@ -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);

View file

@ -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>
);

View file

@ -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));

View file

@ -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}

View file

@ -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...

View file

@ -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;

View file

@ -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')}

View file

@ -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()}

View file

@ -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 }} />}

View file

@ -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}`} />

View file

@ -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>
)}

View file

@ -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>

View file

@ -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));

View file

@ -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" />}

View file

@ -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);

View file

@ -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>

View file

@ -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,

View file

@ -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);

View file

@ -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) {

View file

@ -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 {

View file

@ -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;
}

View file

@ -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"