WIP Checkpoint, spaghetti code!
This commit is contained in:
parent
5ea7dde6f4
commit
63806845ca
25 changed files with 2113 additions and 859 deletions
2
.babelrc
2
.babelrc
|
@ -1,6 +1,8 @@
|
|||
{
|
||||
"presets": ["@babel/react", "@babel/flow"],
|
||||
"plugins": [
|
||||
"import-glob",
|
||||
"@babel/plugin-transform-runtime",
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"react-hot-loader/babel",
|
||||
["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }],
|
||||
|
|
18
package.json
18
package.json
|
@ -25,7 +25,7 @@
|
|||
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
|
||||
"dev": "yarn dev:electron",
|
||||
"dev:electron": "cross-env NODE_ENV=development node ./src/platforms/electron/devServer.js",
|
||||
"dev:web": "NODE_ENV=development webpack-dev-server --open --hot --progress --config webpack.web.config.js",
|
||||
"dev:web": "cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --config webpack.web.config.js",
|
||||
"dev:internal-apis": "LBRY_API_URL='http://localhost:9090' yarn dev:electron",
|
||||
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js",
|
||||
"pack": "electron-builder --dir",
|
||||
|
@ -52,6 +52,7 @@
|
|||
"@babel/plugin-proposal-decorators": "^7.3.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-flow-strip-types": "^7.2.3",
|
||||
"@babel/plugin-transform-runtime": "^7.4.3",
|
||||
"@babel/polyfill": "^7.2.5",
|
||||
"@babel/preset-flow": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
|
@ -64,8 +65,11 @@
|
|||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-add-module-exports": "^1.0.0",
|
||||
"babel-plugin-import-glob": "^2.0.0",
|
||||
"babel-plugin-transform-imports": "^1.5.1",
|
||||
"bluebird": "^3.5.1",
|
||||
"butterchurn": "^2.6.7",
|
||||
"butterchurn-presets": "^2.4.7",
|
||||
"chalk": "^2.4.2",
|
||||
"classnames": "^2.2.5",
|
||||
"codemirror": "^5.39.2",
|
||||
|
@ -74,6 +78,7 @@
|
|||
"country-data": "^0.0.31",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^2.1.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"dat.gui": "^0.7.2",
|
||||
"decompress": "^4.2.0",
|
||||
"del": "^3.0.0",
|
||||
|
@ -106,12 +111,14 @@
|
|||
"hast-util-sanitize": "^1.1.2",
|
||||
"history": "^4.9.0",
|
||||
"husky": "^0.14.3",
|
||||
"jsmediatags": "^3.8.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#86f1340f834d0f5cd5365492a8ff15d4b213a050",
|
||||
"lbryinc": "lbryio/lbryinc#d9f9035113c8b9ab3b0ee7ffbd38f910086a665e",
|
||||
"lbry-redux": "lbryio/lbry-redux#d4c7dea65f7179974e9b96c863022fe7b049ff7d",
|
||||
"lbryinc": "lbryio/lbryinc#4f2d4a50986bffab0b05d9f6cd7c2f0a856a0e02",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
"lodash-es": "^4.17.11",
|
||||
"make-runnable": "^1.3.6",
|
||||
"mammoth": "^1.4.6",
|
||||
"mime": "^2.3.1",
|
||||
|
@ -121,6 +128,8 @@
|
|||
"node-libs-browser": "^2.1.0",
|
||||
"node-loader": "^0.6.0",
|
||||
"node-sass": "^4.11.0",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"preprocess-loader": "^0.3.0",
|
||||
"prettier": "^1.11.1",
|
||||
"prop-types": "^15.6.2",
|
||||
|
@ -156,9 +165,10 @@
|
|||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"three": "^0.93.0",
|
||||
"three-full": "^11.3.2",
|
||||
"three-full": "^17.1.0",
|
||||
"tree-kill": "^1.1.0",
|
||||
"video.js": "^7.2.2",
|
||||
"wavesurfer.js": "^2.2.1",
|
||||
"webpack": "^4.28.4",
|
||||
"webpack-bundle-analyzer": "^3.1.0",
|
||||
"webpack-config-utils": "^2.3.1",
|
||||
|
|
16
postcss.config.js
Normal file
16
postcss.config.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
module.exports = ({ file, options, env }) => {
|
||||
env = env || {};
|
||||
file = file || {};
|
||||
options = options || {};
|
||||
options.cssnext = options.cssnext || null;
|
||||
options.autoprefixer = options.autoprefixer || null;
|
||||
options.cssnano = options.cssnano || null;
|
||||
|
||||
return {
|
||||
parser: file.extname === '.sss' ? 'sugarss' : false,
|
||||
plugins: {
|
||||
'postcss-import': { root: file.dirname },
|
||||
'cssnano': env === 'production' ? options.cssnano : false
|
||||
}
|
||||
};
|
||||
};
|
|
@ -40,12 +40,8 @@ app.setName('LBRY');
|
|||
app.setAppUserModelId('io.lbry.LBRY');
|
||||
|
||||
if (isDev) {
|
||||
// Enable WEBGL
|
||||
app.commandLine.appendSwitch('ignore-gpu-blacklist');
|
||||
app.commandLine.appendSwitch('--disable-gpu-process-crash-limit');
|
||||
app.disableDomainBlockingFor3DAPIs();
|
||||
|
||||
// Disable security warnings in dev mode - https://github.com/electron/electron/blob/master/docs/tutorial/security.md#electron-security-warnings
|
||||
// Disable security warnings in dev mode:
|
||||
// https://github.com/electron/electron/blob/master/docs/tutorial/security.md#electron-security-warnings
|
||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import ReactDOMServer from 'react-dom/server';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
import 'easymde/dist/easymde.min.css';
|
||||
import Toggle from 'react-toggle';
|
||||
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
||||
|
||||
const SimpleMDE = React.lazy(() => import(
|
||||
/* webpackChunkName: "SimpleMDE" */
|
||||
'react-simplemde-editor'
|
||||
));
|
||||
|
||||
type Props = {
|
||||
name: string,
|
||||
label?: string,
|
||||
|
@ -132,6 +136,7 @@ export class FormField extends React.PureComponent<Props> {
|
|||
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
|
||||
<fieldset-section>
|
||||
<label htmlFor={name}>{label}</label>
|
||||
<Suspense fallback={<div></div>}>
|
||||
<SimpleMDE
|
||||
{...inputProps}
|
||||
id={name}
|
||||
|
@ -145,6 +150,7 @@ export class FormField extends React.PureComponent<Props> {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</fieldset-section>
|
||||
</div>
|
||||
);
|
||||
|
|
55
src/ui/component/common/markdown-preview-internal.jsx
Normal file
55
src/ui/component/common/markdown-preview-internal.jsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import remark from 'remark';
|
||||
import reactRenderer from 'remark-react';
|
||||
import remarkEmoji from 'remark-emoji';
|
||||
import ExternalLink from 'component/externalLink';
|
||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||
|
||||
type MarkdownProps = {
|
||||
content: ?string,
|
||||
promptLinks?: boolean,
|
||||
};
|
||||
|
||||
type SimpleLinkProps = {
|
||||
href?: string,
|
||||
title?: string,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
const SimpleLink = (props: SimpleLinkProps) => {
|
||||
const { href, title, children } = props;
|
||||
return (
|
||||
<a href={href} title={title}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
// Use github sanitation schema
|
||||
const schema = { ...defaultSchema };
|
||||
|
||||
// Extend sanitation schema to support lbry protocol
|
||||
schema.protocols.href[3] = 'lbry';
|
||||
|
||||
const MarkdownPreview = (props: MarkdownProps) => {
|
||||
const { content, promptLinks } = props;
|
||||
const remarkOptions = {
|
||||
sanitize: schema,
|
||||
remarkReactComponents: {
|
||||
a: promptLinks ? ExternalLink : SimpleLink,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div className="markdown-preview">
|
||||
{
|
||||
remark()
|
||||
.use(remarkEmoji)
|
||||
.use(reactRenderer, remarkOptions)
|
||||
.processSync(content).contents
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkdownPreview;
|
|
@ -1,54 +1,16 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import remark from 'remark';
|
||||
import reactRenderer from 'remark-react';
|
||||
import remarkEmoji from 'remark-emoji';
|
||||
import ExternalLink from 'component/externalLink';
|
||||
import defaultSchema from 'hast-util-sanitize/lib/github.json';
|
||||
import React, { Suspense } from 'react';
|
||||
|
||||
type MarkdownProps = {
|
||||
content: ?string,
|
||||
promptLinks?: boolean,
|
||||
};
|
||||
const MarkdownPreviewInternal = React.lazy(() => import(
|
||||
/* webpackChunkName: "markdownPreview" */
|
||||
/* webpackPrefetch: true */
|
||||
'./markdown-preview-internal'
|
||||
));
|
||||
|
||||
type SimpleLinkProps = {
|
||||
href?: string,
|
||||
title?: string,
|
||||
children?: React.Node,
|
||||
};
|
||||
|
||||
const SimpleLink = (props: SimpleLinkProps) => {
|
||||
const { href, title, children } = props;
|
||||
const MarkdownPreview = (props) => {
|
||||
return (
|
||||
<a href={href} title={title}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
// Use github sanitation schema
|
||||
const schema = { ...defaultSchema };
|
||||
|
||||
// Extend sanitation schema to support lbry protocol
|
||||
schema.protocols.href[3] = 'lbry';
|
||||
|
||||
const MarkdownPreview = (props: MarkdownProps) => {
|
||||
const { content, promptLinks } = props;
|
||||
const remarkOptions = {
|
||||
sanitize: schema,
|
||||
remarkReactComponents: {
|
||||
a: promptLinks ? ExternalLink : SimpleLink,
|
||||
},
|
||||
};
|
||||
return (
|
||||
<div className="markdown-preview">
|
||||
{
|
||||
remark()
|
||||
.use(remarkEmoji)
|
||||
.use(reactRenderer, remarkOptions)
|
||||
.processSync(content).contents
|
||||
}
|
||||
</div>
|
||||
<Suspense fallback={<div className="markdown-preview"></div>}>
|
||||
<MarkdownPreviewInternal {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import QRCodeElement from 'qrcode.react';
|
||||
import React, { Suspense } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const LazyQRCodeElement = React.lazy(() => import(
|
||||
/* webpackChunkName: "qrCode" */
|
||||
'qrcode.react'
|
||||
));
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
paddingRight?: boolean,
|
||||
|
@ -24,7 +28,9 @@ class QRCode extends React.Component<Props> {
|
|||
'qr-code--top-padding': paddingTop,
|
||||
})}
|
||||
>
|
||||
<QRCodeElement value={value} />
|
||||
<Suspense fallback={<div></div>}>
|
||||
<LazyQRCodeElement value={value} />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,35 @@
|
|||
// @flow
|
||||
import type { Claim } from 'types/claim';
|
||||
import { remote } from 'electron';
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import PdfViewer from 'component/viewers/pdfViewer';
|
||||
import DocumentViewer from 'component/viewers/documentViewer';
|
||||
import DocxViewer from 'component/viewers/docxViewer';
|
||||
import HtmlViewer from 'component/viewers/htmlViewer';
|
||||
import AudioVideoViewer from 'component/viewers/audioVideoViewer';
|
||||
import VideoViewer from 'component/viewers/videoViewer';
|
||||
|
||||
const AudioViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "audioViewer" */
|
||||
'component/viewers/audioViewer'
|
||||
));
|
||||
|
||||
const DocumentViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "documentViewer" */
|
||||
'component/viewers/documentViewer'
|
||||
));
|
||||
|
||||
const DocxViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "docxViewer" */
|
||||
'component/viewers/docxViewer'
|
||||
));
|
||||
|
||||
const HtmlViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "htmlViewer" */
|
||||
'component/viewers/htmlViewer'
|
||||
));
|
||||
|
||||
const PdfViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "pdfViewer" */
|
||||
'component/viewers/pdfViewer'
|
||||
));
|
||||
|
||||
// @if TARGET='app'
|
||||
const ThreeViewer = React.lazy(() => import(
|
||||
/* webpackChunkName: "threeViewer" */
|
||||
|
@ -97,6 +119,8 @@ class FileRender extends React.PureComponent<Props> {
|
|||
renderViewer() {
|
||||
const { source, mediaType, currentTheme, poster, claim } = this.props;
|
||||
|
||||
console.log('mediaType', mediaType);
|
||||
|
||||
// Extract relevant data to render file
|
||||
const { stream, fileType, contentType, downloadPath, fileName } = source;
|
||||
|
||||
|
@ -123,7 +147,7 @@ class FileRender extends React.PureComponent<Props> {
|
|||
/>
|
||||
),
|
||||
video: (
|
||||
<AudioVideoViewer
|
||||
<VideoViewer
|
||||
claim={claim}
|
||||
source={{ downloadPath, fileName }}
|
||||
contentType={contentType}
|
||||
|
@ -131,7 +155,7 @@ class FileRender extends React.PureComponent<Props> {
|
|||
/>
|
||||
),
|
||||
audio: (
|
||||
<AudioVideoViewer
|
||||
<AudioViewer
|
||||
claim={claim}
|
||||
source={{ downloadPath, fileName }}
|
||||
contentType={contentType}
|
||||
|
@ -176,6 +200,7 @@ class FileRender extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
console.log('RENDER')
|
||||
return (
|
||||
<div className="file-render">
|
||||
<React.Suspense fallback={<div></div>}>
|
||||
|
|
|
@ -47,11 +47,8 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
'comic-book',
|
||||
'document',
|
||||
'3D-file',
|
||||
// The web can use the new video player, which has it's own file renderer
|
||||
// @if TARGET='web'
|
||||
'video',
|
||||
'audio',
|
||||
// @endif
|
||||
];
|
||||
static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/';
|
||||
static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278';
|
||||
|
@ -266,6 +263,11 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
// This files are supported using a custom viewer
|
||||
const { mediaType, contentType } = this.props;
|
||||
|
||||
console.log({
|
||||
mediaType,
|
||||
contentType
|
||||
})
|
||||
|
||||
return (
|
||||
MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1 ||
|
||||
MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1
|
||||
|
@ -360,6 +362,13 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
const isPlayableType = this.playableType();
|
||||
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
||||
|
||||
console.log({
|
||||
mediaType,
|
||||
fileSource,
|
||||
isFileReady,
|
||||
isFileType
|
||||
})
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{loadingStatus && <LoadingScreen status={loadingStatus} spinner={isLoading} />}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import analytics from 'analytics';
|
||||
import type { Claim } from 'types/claim';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import Player from './internal/player';
|
||||
import PlayButton from './internal/play-button';
|
||||
|
||||
const Player = React.lazy(() => import(
|
||||
/* webpackChunkName: "player-legacy" */
|
||||
'./internal/player'
|
||||
));
|
||||
|
||||
const SPACE_BAR_KEYCODE = 32;
|
||||
|
||||
type Props = {
|
||||
|
@ -269,6 +273,7 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
<LoadingScreen status={loadStatusMessage} />
|
||||
</div>
|
||||
) : (
|
||||
<Suspense fallback={<div></div>}>
|
||||
<Player
|
||||
fileName={fileInfo.file_name}
|
||||
poster={poster}
|
||||
|
@ -288,6 +293,7 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
onFinishCb={this.onFileFinishCb}
|
||||
playingUri={playingUri}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
|
277
src/ui/component/viewers/audioViewer.jsx
Normal file
277
src/ui/component/viewers/audioViewer.jsx
Normal file
|
@ -0,0 +1,277 @@
|
|||
// @flow
|
||||
import type { Claim } from 'types/claim';
|
||||
import React from 'react';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import Button from 'component/button';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
import { stopContextMenu } from 'util/context-menu';
|
||||
import butterchurn from 'butterchurn';
|
||||
import detectButterchurnSupport from 'butterchurn/lib/isSupported.min';
|
||||
import butterchurnPresets from 'butterchurn-presets';
|
||||
import jsmediatags from 'jsmediatags/dist/jsmediatags';
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
|
||||
import styles from './audioViewer.module.scss';
|
||||
|
||||
const isButterchurnSupported = detectButterchurnSupport();
|
||||
|
||||
const EQ_BANDS_SIMPLE = [
|
||||
55,
|
||||
150,
|
||||
250,
|
||||
400,
|
||||
500,
|
||||
1000,
|
||||
2000,
|
||||
4000,
|
||||
8000,
|
||||
16000,
|
||||
]
|
||||
/*
|
||||
const EQ_LOWSHELF = EQ_BANDS_SIMPLE.shift();
|
||||
const EQ_HIGHSHELF = EQ_BANDS_SIMPLE.pop();
|
||||
|
||||
const eqFilters = EQ.map(function(band) {
|
||||
var filter = wavesurfer.backend.ac.createBiquadFilter();
|
||||
filter.type = 'peaking';
|
||||
filter.gain.value = 0;
|
||||
filter.Q.value = 1;
|
||||
filter.frequency.value = band.f;
|
||||
return filter;
|
||||
});
|
||||
*/
|
||||
|
||||
type Props = {
|
||||
source: {
|
||||
downloadPath: string,
|
||||
fileName: string,
|
||||
},
|
||||
contentType: string,
|
||||
poster?: string,
|
||||
claim: Claim,
|
||||
};
|
||||
|
||||
const presets = [
|
||||
require('butterchurn-presets/presets/converted/Flexi - when monopolies were the future [simple warp + non-reactive moebius].json'),
|
||||
require('butterchurn-presets/presets/converted/Rovastar & Loadus - FractalDrop (Active Sparks Mix).json'),
|
||||
require('butterchurn-presets/presets/converted/shifter - tumbling cubes (ripples).json'),
|
||||
require('butterchurn-presets/presets/converted/ORB - Blue Emotion.json'),
|
||||
require('butterchurn-presets/presets/converted/shifter - urchin mod.json'),
|
||||
require('butterchurn-presets/presets/converted/Stahlregen & fishbrain + flexi + geiss - The Machine that conquered the Aether.json'),
|
||||
require('butterchurn-presets/presets/converted/Zylot - Crosshair Dimension (Light of Ages).json'),
|
||||
];
|
||||
|
||||
class AudioVideoViewer extends React.PureComponent<Props> {
|
||||
audioNode: ?HTMLAudioElement;
|
||||
player: ?{ dispose: () => void };
|
||||
|
||||
state = {
|
||||
playing: false,
|
||||
enableMilkdrop: isButterchurnSupported,
|
||||
showEqualizer: false,
|
||||
showSongDetails: true,
|
||||
enableArt: true,
|
||||
artLoaded: false,
|
||||
artist: null,
|
||||
title: null,
|
||||
album: null,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const me = this;
|
||||
const { contentType, poster, claim } = me.props;
|
||||
|
||||
const path = `https://api.lbry.tv/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`;
|
||||
const sources = [
|
||||
{
|
||||
src: path,
|
||||
type: contentType,
|
||||
},
|
||||
];
|
||||
|
||||
const audioNode = this.audioNode;
|
||||
|
||||
audioNode.crossOrigin = 'anonymous';
|
||||
|
||||
const canvasHeight = me.canvasNode.offsetHeight;
|
||||
const canvasWidth = me.canvasNode.offsetWidth;
|
||||
|
||||
// Required for canvas, nuance of rendering
|
||||
me.canvasNode.height = canvasHeight;
|
||||
me.canvasNode.width = canvasWidth;
|
||||
|
||||
const AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
const audioContext = new AudioContext();
|
||||
|
||||
const audioSource = audioContext.createMediaElementSource(audioNode);
|
||||
audioSource.connect(audioContext.destination);
|
||||
|
||||
if (isButterchurnSupported) {
|
||||
const visualizer = me.visualizer = butterchurn.createVisualizer(audioContext, me.canvasNode, {
|
||||
height: canvasHeight,
|
||||
width: canvasWidth,
|
||||
pixelRatio: window.devicePixelRatio || 1,
|
||||
textureRatio: 1,
|
||||
});
|
||||
|
||||
visualizer.connectAudio(audioSource);
|
||||
visualizer.loadPreset(presets[Math.floor(Math.random() * presets.length)], 2.0);
|
||||
|
||||
me._frameCycle = () => {
|
||||
requestAnimationFrame(me._frameCycle);
|
||||
|
||||
if(me.state.enableMilkdrop === true) {
|
||||
visualizer.render();
|
||||
}
|
||||
}
|
||||
me._frameCycle();
|
||||
}
|
||||
|
||||
const wavesurfer = WaveSurfer.create({
|
||||
barWidth: 3,
|
||||
container: this.waveNode,
|
||||
waveColor: '#000',
|
||||
progressColor: '#fff',
|
||||
mediaControls: true,
|
||||
responsive: true,
|
||||
normalize: true,
|
||||
backend: 'MediaElement',
|
||||
minPxPerSec: 100,
|
||||
height: this.waveNode.offsetHeight,
|
||||
});
|
||||
|
||||
wavesurfer.load(audioNode);
|
||||
|
||||
jsmediatags.Config.setDisallowedXhrHeaders(['If-Modified-Since', 'Range']);
|
||||
jsmediatags.read(path, {
|
||||
onSuccess: function(result) {
|
||||
const {
|
||||
album,
|
||||
artist,
|
||||
title,
|
||||
picture
|
||||
} = result.tags;
|
||||
|
||||
if (picture) {
|
||||
const byteArray = new Uint8Array(picture.data);
|
||||
const blob = new Blob([byteArray], { type: picture.type });
|
||||
const albumArtUrl = URL.createObjectURL(blob);
|
||||
me.artNode.src = albumArtUrl;
|
||||
|
||||
me.setState({ artLoaded: true });
|
||||
}
|
||||
|
||||
me.setState({
|
||||
album,
|
||||
artist,
|
||||
title,
|
||||
});
|
||||
},
|
||||
onError: function(error) {
|
||||
console.log(':(', error.type, error.info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.player) {
|
||||
this.player.dispose();
|
||||
}
|
||||
|
||||
// Kill the render loop
|
||||
this._frameCycle = () => {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const me = this;
|
||||
const { contentType, poster, claim } = me.props;
|
||||
const {
|
||||
album,
|
||||
artist,
|
||||
title,
|
||||
enableMilkdrop,
|
||||
showEqualizer,
|
||||
showSongDetails,
|
||||
enableArt,
|
||||
artLoaded,
|
||||
playing,
|
||||
userActive,
|
||||
} = this.state;
|
||||
|
||||
const renderArt = enableArt && artLoaded;
|
||||
|
||||
const path = `https://api.lbry.tv/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`;
|
||||
|
||||
const playButton = (
|
||||
<div onClick={()=>{
|
||||
const audioNode = this.audioNode;
|
||||
if (audioNode.paused) {
|
||||
audioNode.play();
|
||||
} else {
|
||||
audioNode.pause();
|
||||
}
|
||||
}} className={playing ? styles.playButtonPause : styles.playButtonPlay}></div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={userActive ? styles.userActive : styles.wrapper} onMouseEnter={()=>me.setState({ userActive: true })} onMouseLeave={()=>me.setState({ userActive: false })} onContextMenu={stopContextMenu}>
|
||||
<div className={enableMilkdrop ? styles.containerWithMilkdrop : styles.container}>
|
||||
<div style={{ position: 'absolute', top: 0, right: 0 }}>
|
||||
<Tooltip onComponent body={__('Toggle Visualizer')}>
|
||||
<Button
|
||||
icon={enableMilkdrop ? ICONS.VISUALIZER_ON : ICONS.VISUALIZER_OFF}
|
||||
onClick={() => {
|
||||
if (!isButterchurnSupported) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get new preset
|
||||
this.visualizer.loadPreset(presets[Math.floor(Math.random() * presets.length)], 2.0);
|
||||
|
||||
this.setState({ enableMilkdrop: !enableMilkdrop })
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip onComponent body={__('Toggle Album Art')}>
|
||||
<Button
|
||||
icon={enableArt ? ICONS.MUSIC_ART_ON : ICONS.MUSIC_ART_OFF}
|
||||
onClick={() => this.setState({ enableArt: !enableArt })}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip onComponent body={__('Toggle Details')}>
|
||||
<Button
|
||||
icon={showSongDetails ? ICONS.MUSIC_DETAILS_ON : ICONS.MUSIC_DETAILS_OFF}
|
||||
onClick={() => this.setState({ showSongDetails: !showSongDetails })}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip onComponent body={__('Equalizer')}>
|
||||
<Button
|
||||
icon={ICONS.MUSIC_EQUALIZER}
|
||||
onClick={() => this.setState({ showEqualizer: !showEqualizer })}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div ref={node => (this.waveNode = node)} className={styles.wave}></div>
|
||||
<div className={styles.infoContainer}>
|
||||
<div className={renderArt ? styles.infoArtContainer : styles.infoArtContainerHidden}>
|
||||
<img className={styles.infoArtImage} ref={node => (this.artNode = node)} />
|
||||
{renderArt && playButton}
|
||||
</div>
|
||||
<div className={showSongDetails ? (renderArt ? styles.songDetailsContainer : styles.songDetailsContainerNoArt) : styles.songDetailsContainerHidden}>
|
||||
<div className={renderArt ? styles.songDetails : styles.songDetailsNoArt}>
|
||||
{artist && <div className={styles.detailsLineArtist}><Button icon={ICONS.MUSIC_ARTIST} className={styles.detailsIconArtist} />{artist}</div>}
|
||||
{title && <div className={styles.detailsLineSong}><Button icon={ICONS.MUSIC_SONG} className={styles.detailsIconSong} />{title}</div>}
|
||||
{album && <div className={styles.detailsLineAlbum}><Button icon={ICONS.MUSIC_ALBUM} className={styles.detailsIconAlbum} />{album}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{!renderArt && <div className={styles.playButtonDetachedContainer}>{playButton}</div>}
|
||||
</div>
|
||||
<canvas ref={node => (this.canvasNode = node)} className={enableMilkdrop ? styles.milkdrop : styles.milkdropDisabled} />
|
||||
<audio ref={node => (this.audioNode = node)} src={path} style={{ position: 'absolute', top: '-100px' }} onPlay={()=>this.setState({ playing: true })} onPause={()=>this.setState({ playing: false })} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AudioVideoViewer;
|
193
src/ui/component/viewers/audioViewer.module.scss
Normal file
193
src/ui/component/viewers/audioViewer.module.scss
Normal file
|
@ -0,0 +1,193 @@
|
|||
.wrapper {
|
||||
composes: 'file-render__viewer' from global;
|
||||
}
|
||||
|
||||
.userActive {
|
||||
composes: wrapper;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: #212529;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.containerWithMilkdrop {
|
||||
composes: container;
|
||||
|
||||
background: rgba(50, 50, 55, .7);
|
||||
}
|
||||
|
||||
.wave {
|
||||
position: absolute;
|
||||
bottom: -20%;
|
||||
height: 40%;
|
||||
opacity: 0.5;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
padding: 0 20%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 42%;
|
||||
align-self: center;
|
||||
width: 100%;
|
||||
margin-top: -10%;
|
||||
}
|
||||
|
||||
.infoArtContainer {
|
||||
align-self: flex-start;
|
||||
width: 40%;
|
||||
float: left;
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0 , 0.4);
|
||||
}
|
||||
|
||||
.infoArtContainerHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.infoArtImage {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
transition: opacity 0.7s;
|
||||
|
||||
.userActive & {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.songDetailsContainer {
|
||||
text-align: left;
|
||||
padding: 3%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.songDetailsContainerHidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.songDetailsContainerNoArt {
|
||||
composes: songDetailsContainer;
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.songDetails {
|
||||
width: 150%;
|
||||
text-shadow: 2px 2px 3px #000;
|
||||
}
|
||||
|
||||
.songDetailsNoArt {
|
||||
composes: songDetails;
|
||||
|
||||
width: 200%;
|
||||
margin-left: -50%;
|
||||
}
|
||||
|
||||
.detailsIcon {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
top: -3px;
|
||||
padding-right: 10px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.detailsIconArtist {
|
||||
composes: detailsIcon;
|
||||
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
.detailsIconSong {
|
||||
composes: detailsIcon;
|
||||
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.detailsIconAlbum {
|
||||
composes: detailsIcon;
|
||||
}
|
||||
|
||||
.detailsLineArtist {
|
||||
font-size: 26px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.detailsLineSong {
|
||||
font-size: 34px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.detailsLineAlbum {
|
||||
font-size: 20px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.playButton {
|
||||
position: absolute;
|
||||
border: 5px solid #fff;
|
||||
border-radius: 45px;
|
||||
color: #fff;
|
||||
font-family: arial;
|
||||
font-size: 60px;
|
||||
left: 50%;
|
||||
line-height: 80px;
|
||||
margin-left: -45px;
|
||||
padding-left: 20px;
|
||||
bottom: 50%;
|
||||
margin-bottom: -45px;
|
||||
height: 90px;
|
||||
width: 90px;
|
||||
opacity: 0;
|
||||
transition: opacity .7s;
|
||||
|
||||
.userActive & {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.playButtonPlay {
|
||||
composes: playButton;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: "▶";
|
||||
}
|
||||
}
|
||||
|
||||
.playButtonPause {
|
||||
composes: playButton;
|
||||
|
||||
font-size: 50px;
|
||||
line-height: 75px;
|
||||
padding-left: 20px;
|
||||
letter-spacing: -24px;
|
||||
|
||||
&::after {
|
||||
display: block;
|
||||
content: "▎▎";
|
||||
}
|
||||
}
|
||||
|
||||
.playButtonDetachedContainer {
|
||||
bottom: 35%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.milkdrop {
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.milkdropDisabled {
|
||||
display: none;
|
||||
}
|
|
@ -36,7 +36,7 @@ class CodeViewer extends React.PureComponent<Props> {
|
|||
const { theme, contentType } = me.props;
|
||||
// Init CodeMirror
|
||||
import(
|
||||
/* webpackChunkName: "codeViewer" */
|
||||
/* webpackChunkName: "codemirror" */
|
||||
'codemirror/lib/codemirror'
|
||||
).then((CodeMirror) => {
|
||||
me.codeMirror = CodeMirror.fromTextArea(me.textarea, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import MarkdownPreview from 'component/common/markdown-preview';
|
||||
|
||||
|
@ -84,7 +84,7 @@ class DocumentViewer extends React.PureComponent<Props, State> {
|
|||
<div className="file-render__viewer document-viewer">
|
||||
{loading && !error && <LoadingScreen status={loadingMessage} spinner />}
|
||||
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
|
||||
{isReady && this.renderDocument()}
|
||||
{isReady && <Suspense fallback={<div></div>}>{this.renderDocument()}</Suspense>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// @flow
|
||||
import type { Claim } from 'types/claim';
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { stopContextMenu } from 'util/context-menu';
|
||||
import(/* webpackChunkName: "videojs" */
|
||||
import(
|
||||
/* webpackChunkName: "videojs" */
|
||||
/* webpackPreload: true */
|
||||
'video.js/dist/video-js.css');
|
||||
'video.js/dist/video-js.css'
|
||||
);
|
||||
|
||||
type Props = {
|
||||
source: {
|
||||
|
@ -39,11 +41,16 @@ class AudioVideoViewer extends React.PureComponent<Props> {
|
|||
sources,
|
||||
};
|
||||
|
||||
import(/* webpackChunkName: "videojs" */
|
||||
import(
|
||||
/* webpackChunkName: "videojs" */
|
||||
/* webpackMode: "lazy" */
|
||||
/* webpackPreload: true */
|
||||
'video.js').then(videojs => {
|
||||
this.player = videojs.default(this.videoNode, videoJsOptions, () => {});
|
||||
'video.js'
|
||||
).then(videojs => {
|
||||
if (videojs.__esModule) {
|
||||
videojs = videojs.default;
|
||||
}
|
||||
this.player = videojs(this.videoNode, videoJsOptions, () => {});
|
||||
});
|
||||
}
|
||||
|
|
@ -53,3 +53,13 @@ export const TRANSACTIONS = 'FileText';
|
|||
export const LBRY = 'Lbry';
|
||||
export const SEND = 'Send';
|
||||
export const DISCOVER = 'Compass';
|
||||
export const VISUALIZER_ON = 'Eye';
|
||||
export const VISUALIZER_OFF = 'EyeOff';
|
||||
export const MUSIC_DETAILS_ON = 'AlignLeft';
|
||||
export const MUSIC_DETAILS_OFF = 'AlignLeft';
|
||||
export const MUSIC_ART_ON = 'Image';
|
||||
export const MUSIC_ART_OFF = 'Image';
|
||||
export const MUSIC_ALBUM = 'Disc';
|
||||
export const MUSIC_ARTIST = 'Mic';
|
||||
export const MUSIC_SONG = 'Music';
|
||||
export const MUSIC_EQUALIZER = 'Sliders';
|
||||
|
|
|
@ -21,7 +21,11 @@ import {
|
|||
import { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux';
|
||||
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||
import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc';
|
||||
import 'scss/all.scss';
|
||||
import(
|
||||
/* webpackChunkName: "styles" */
|
||||
/* webpackPrefetch: true */
|
||||
'scss/all.scss'
|
||||
);
|
||||
import { store, history } from 'store';
|
||||
import pjson from 'package.json';
|
||||
import app from './app';
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import { Modal } from 'modal/modal';
|
||||
import Button from 'component/button';
|
||||
import UserPhoneNew from 'component/userPhoneNew';
|
||||
import UserPhoneVerify from 'component/userPhoneVerify';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
const LazyUserPhoneNew = React.lazy(() => import(
|
||||
/* webpackChunkName: "userPhoneNew" */
|
||||
'component/userPhoneNew'
|
||||
));
|
||||
|
||||
type Props = {
|
||||
phone: ?number,
|
||||
user: {
|
||||
|
@ -31,7 +35,11 @@ class ModalPhoneCollection extends React.PureComponent<Props> {
|
|||
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
|
||||
|
||||
if (!user.phone_number && !phone) {
|
||||
return <UserPhoneNew cancelButton={cancelButton} />;
|
||||
return (
|
||||
<Suspense fallback={<div></div>}>
|
||||
<UserPhoneNew cancelButton={cancelButton} />
|
||||
</Suspense>
|
||||
);
|
||||
} else if (!user.phone_number) {
|
||||
return <UserPhoneVerify cancelButton={cancelButton} />;
|
||||
}
|
||||
|
|
|
@ -147,6 +147,11 @@ class FilePage extends React.Component<Props> {
|
|||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||
const fileName = fileInfo ? fileInfo.file_name : null;
|
||||
const mediaType = getMediaType(contentType, fileName);
|
||||
console.log({
|
||||
mediaType,
|
||||
contentType,
|
||||
fileName,
|
||||
})
|
||||
const showFile =
|
||||
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@ import {
|
|||
parseURI,
|
||||
creditsToString,
|
||||
doError,
|
||||
makeSelectCostInfoForUri,
|
||||
} from 'lbry-redux';
|
||||
import {
|
||||
makeSelectCostInfoForUri,
|
||||
} from 'lbryinc';
|
||||
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||
import analytics from 'analytics';
|
||||
import { formatLbryUriForWeb } from 'util/uri';
|
||||
|
|
|
@ -16,7 +16,6 @@ import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subs
|
|||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { Lbry, buildURI, parseURI, doResolveUris } from 'lbry-redux';
|
||||
import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content';
|
||||
import Promise from 'bluebird';
|
||||
|
||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
||||
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
||||
|
|
|
@ -3,7 +3,6 @@ const webpack = require('webpack');
|
|||
const merge = require('webpack-merge');
|
||||
const { DefinePlugin, ProvidePlugin } = require('webpack');
|
||||
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||
|
@ -41,11 +40,27 @@ let baseConfig = {
|
|||
loader: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
test: /\.module.scss$/,
|
||||
use: [
|
||||
'style-loader', // creates style nodes from JS strings
|
||||
'css-loader', // translates CSS into CommonJS
|
||||
'sass-loader', // compiles Sass to CSS, using Node Sass by default
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.s?css$/,
|
||||
exclude: /\.module.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -79,12 +94,24 @@ let baseConfig = {
|
|||
resolve: {
|
||||
modules: [UI_ROOT, 'node_modules', __dirname],
|
||||
extensions: ['.js', '.jsx', '.json', '.scss'],
|
||||
alias: {
|
||||
'lbry-redux$': 'lbry-redux/dist/bundle.es.js',
|
||||
|
||||
// Build optimizations for 'redux-persist-transform-filter'
|
||||
'redux-persist-transform-filter': 'redux-persist-transform-filter/index.js',
|
||||
'lodash.get': 'lodash-es/get',
|
||||
'lodash.set': 'lodash-es/set',
|
||||
'lodash.unset': 'lodash-es/unset',
|
||||
'lodash.pickby': 'lodash-es/pickBy',
|
||||
'lodash.isempty': 'lodash-es/isEmpty',
|
||||
'lodash.forin': 'lodash-es/forIn',
|
||||
'lodash.clonedeep': 'lodash-es/cloneDeep',
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||
new webpack.EnvironmentPlugin(['NODE_ENV']),
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new ProvidePlugin({
|
||||
i18n: ['i18n', 'default'],
|
||||
__: ['i18n/__', 'default'],
|
||||
|
|
|
@ -5,6 +5,7 @@ const baseConfig = require('./webpack.base.config.js');
|
|||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const { DefinePlugin } = require('webpack');
|
||||
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
|
||||
const STATIC_ROOT = path.resolve(__dirname, 'static/');
|
||||
const DIST_ROOT = path.resolve(__dirname, 'dist/');
|
||||
|
@ -108,6 +109,7 @@ const renderConfig = {
|
|||
],
|
||||
},
|
||||
plugins: [
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new DefinePlugin({
|
||||
IS_WEB: JSON.stringify(false),
|
||||
}),
|
||||
|
|
Loading…
Reference in a new issue