Merge pull request #2406 from lbryio/audio-player
New Audio Player & Code Splitting
This commit is contained in:
commit
627acbd885
25 changed files with 2113 additions and 859 deletions
2
.babelrc
2
.babelrc
|
@ -1,6 +1,8 @@
|
||||||
{
|
{
|
||||||
"presets": ["@babel/react", "@babel/flow"],
|
"presets": ["@babel/react", "@babel/flow"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
|
"import-glob",
|
||||||
|
"@babel/plugin-transform-runtime",
|
||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"react-hot-loader/babel",
|
"react-hot-loader/babel",
|
||||||
["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }],
|
["@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",
|
"compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web",
|
||||||
"dev": "yarn dev:electron",
|
"dev": "yarn dev:electron",
|
||||||
"dev:electron": "cross-env NODE_ENV=development node ./src/platforms/electron/devServer.js",
|
"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",
|
"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",
|
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js",
|
||||||
"pack": "electron-builder --dir",
|
"pack": "electron-builder --dir",
|
||||||
|
@ -52,6 +52,7 @@
|
||||||
"@babel/plugin-proposal-decorators": "^7.3.0",
|
"@babel/plugin-proposal-decorators": "^7.3.0",
|
||||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||||
"@babel/plugin-transform-flow-strip-types": "^7.2.3",
|
"@babel/plugin-transform-flow-strip-types": "^7.2.3",
|
||||||
|
"@babel/plugin-transform-runtime": "^7.4.3",
|
||||||
"@babel/polyfill": "^7.2.5",
|
"@babel/polyfill": "^7.2.5",
|
||||||
"@babel/preset-flow": "^7.0.0",
|
"@babel/preset-flow": "^7.0.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
|
@ -64,8 +65,11 @@
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.0.1",
|
||||||
"babel-loader": "^8.0.5",
|
"babel-loader": "^8.0.5",
|
||||||
"babel-plugin-add-module-exports": "^1.0.0",
|
"babel-plugin-add-module-exports": "^1.0.0",
|
||||||
|
"babel-plugin-import-glob": "^2.0.0",
|
||||||
"babel-plugin-transform-imports": "^1.5.1",
|
"babel-plugin-transform-imports": "^1.5.1",
|
||||||
"bluebird": "^3.5.1",
|
"bluebird": "^3.5.1",
|
||||||
|
"butterchurn": "^2.6.7",
|
||||||
|
"butterchurn-presets": "^2.4.7",
|
||||||
"chalk": "^2.4.2",
|
"chalk": "^2.4.2",
|
||||||
"classnames": "^2.2.5",
|
"classnames": "^2.2.5",
|
||||||
"codemirror": "^5.39.2",
|
"codemirror": "^5.39.2",
|
||||||
|
@ -74,6 +78,7 @@
|
||||||
"country-data": "^0.0.31",
|
"country-data": "^0.0.31",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^2.1.0",
|
"css-loader": "^2.1.0",
|
||||||
|
"cssnano": "^4.1.10",
|
||||||
"dat.gui": "^0.7.2",
|
"dat.gui": "^0.7.2",
|
||||||
"decompress": "^4.2.0",
|
"decompress": "^4.2.0",
|
||||||
"del": "^3.0.0",
|
"del": "^3.0.0",
|
||||||
|
@ -106,12 +111,14 @@
|
||||||
"hast-util-sanitize": "^1.1.2",
|
"hast-util-sanitize": "^1.1.2",
|
||||||
"history": "^4.9.0",
|
"history": "^4.9.0",
|
||||||
"husky": "^0.14.3",
|
"husky": "^0.14.3",
|
||||||
|
"jsmediatags": "^3.8.1",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||||
"lbry-redux": "lbryio/lbry-redux#86f1340f834d0f5cd5365492a8ff15d4b213a050",
|
"lbry-redux": "lbryio/lbry-redux#d4c7dea65f7179974e9b96c863022fe7b049ff7d",
|
||||||
"lbryinc": "lbryio/lbryinc#d9f9035113c8b9ab3b0ee7ffbd38f910086a665e",
|
"lbryinc": "lbryio/lbryinc#4f2d4a50986bffab0b05d9f6cd7c2f0a856a0e02",
|
||||||
"lint-staged": "^7.0.2",
|
"lint-staged": "^7.0.2",
|
||||||
"localforage": "^1.7.1",
|
"localforage": "^1.7.1",
|
||||||
|
"lodash-es": "^4.17.11",
|
||||||
"make-runnable": "^1.3.6",
|
"make-runnable": "^1.3.6",
|
||||||
"mammoth": "^1.4.6",
|
"mammoth": "^1.4.6",
|
||||||
"mime": "^2.3.1",
|
"mime": "^2.3.1",
|
||||||
|
@ -121,6 +128,8 @@
|
||||||
"node-libs-browser": "^2.1.0",
|
"node-libs-browser": "^2.1.0",
|
||||||
"node-loader": "^0.6.0",
|
"node-loader": "^0.6.0",
|
||||||
"node-sass": "^4.11.0",
|
"node-sass": "^4.11.0",
|
||||||
|
"postcss-import": "^12.0.1",
|
||||||
|
"postcss-loader": "^3.0.0",
|
||||||
"preprocess-loader": "^0.3.0",
|
"preprocess-loader": "^0.3.0",
|
||||||
"prettier": "^1.11.1",
|
"prettier": "^1.11.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
|
@ -156,9 +165,10 @@
|
||||||
"style-loader": "^0.23.1",
|
"style-loader": "^0.23.1",
|
||||||
"terser-webpack-plugin": "^1.2.3",
|
"terser-webpack-plugin": "^1.2.3",
|
||||||
"three": "^0.93.0",
|
"three": "^0.93.0",
|
||||||
"three-full": "^11.3.2",
|
"three-full": "^17.1.0",
|
||||||
"tree-kill": "^1.1.0",
|
"tree-kill": "^1.1.0",
|
||||||
"video.js": "^7.2.2",
|
"video.js": "^7.2.2",
|
||||||
|
"wavesurfer.js": "^2.2.1",
|
||||||
"webpack": "^4.28.4",
|
"webpack": "^4.28.4",
|
||||||
"webpack-bundle-analyzer": "^3.1.0",
|
"webpack-bundle-analyzer": "^3.1.0",
|
||||||
"webpack-config-utils": "^2.3.1",
|
"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');
|
app.setAppUserModelId('io.lbry.LBRY');
|
||||||
|
|
||||||
if (isDev) {
|
if (isDev) {
|
||||||
// Enable WEBGL
|
// Disable security warnings in dev mode:
|
||||||
app.commandLine.appendSwitch('ignore-gpu-blacklist');
|
// https://github.com/electron/electron/blob/master/docs/tutorial/security.md#electron-security-warnings
|
||||||
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
|
|
||||||
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
import MarkdownPreview from 'component/common/markdown-preview';
|
import MarkdownPreview from 'component/common/markdown-preview';
|
||||||
import SimpleMDE from 'react-simplemde-editor';
|
|
||||||
import 'easymde/dist/easymde.min.css';
|
import 'easymde/dist/easymde.min.css';
|
||||||
import Toggle from 'react-toggle';
|
import Toggle from 'react-toggle';
|
||||||
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
||||||
|
|
||||||
|
const SimpleMDE = React.lazy(() => import(
|
||||||
|
/* webpackChunkName: "SimpleMDE" */
|
||||||
|
'react-simplemde-editor'
|
||||||
|
));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
name: string,
|
name: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
|
@ -132,6 +136,7 @@ export class FormField extends React.PureComponent<Props> {
|
||||||
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
|
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<label htmlFor={name}>{label}</label>
|
<label htmlFor={name}>{label}</label>
|
||||||
|
<Suspense fallback={<div></div>}>
|
||||||
<SimpleMDE
|
<SimpleMDE
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
id={name}
|
id={name}
|
||||||
|
@ -145,6 +150,7 @@ export class FormField extends React.PureComponent<Props> {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</Suspense>
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
</div>
|
</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 React, { Suspense } from 'react';
|
||||||
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 = {
|
const MarkdownPreviewInternal = React.lazy(() => import(
|
||||||
content: ?string,
|
/* webpackChunkName: "markdownPreview" */
|
||||||
promptLinks?: boolean,
|
/* webpackPrefetch: true */
|
||||||
};
|
'./markdown-preview-internal'
|
||||||
|
));
|
||||||
|
|
||||||
type SimpleLinkProps = {
|
const MarkdownPreview = (props) => {
|
||||||
href?: string,
|
|
||||||
title?: string,
|
|
||||||
children?: React.Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SimpleLink = (props: SimpleLinkProps) => {
|
|
||||||
const { href, title, children } = props;
|
|
||||||
return (
|
return (
|
||||||
<a href={href} title={title}>
|
<Suspense fallback={<div className="markdown-preview"></div>}>
|
||||||
{children}
|
<MarkdownPreviewInternal {...props} />
|
||||||
</a>
|
</Suspense>
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import QRCodeElement from 'qrcode.react';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
|
const LazyQRCodeElement = React.lazy(() => import(
|
||||||
|
/* webpackChunkName: "qrCode" */
|
||||||
|
'qrcode.react'
|
||||||
|
));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
value: string,
|
value: string,
|
||||||
paddingRight?: boolean,
|
paddingRight?: boolean,
|
||||||
|
@ -24,7 +28,9 @@ class QRCode extends React.Component<Props> {
|
||||||
'qr-code--top-padding': paddingTop,
|
'qr-code--top-padding': paddingTop,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<QRCodeElement value={value} />
|
<Suspense fallback={<div></div>}>
|
||||||
|
<LazyQRCodeElement value={value} />
|
||||||
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,35 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
import { remote } from 'electron';
|
import { remote } from 'electron';
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import PdfViewer from 'component/viewers/pdfViewer';
|
import VideoViewer from 'component/viewers/videoViewer';
|
||||||
import DocumentViewer from 'component/viewers/documentViewer';
|
|
||||||
import DocxViewer from 'component/viewers/docxViewer';
|
const AudioViewer = React.lazy(() => import(
|
||||||
import HtmlViewer from 'component/viewers/htmlViewer';
|
/* webpackChunkName: "audioViewer" */
|
||||||
import AudioVideoViewer from 'component/viewers/audioVideoViewer';
|
'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'
|
// @if TARGET='app'
|
||||||
const ThreeViewer = React.lazy(() => import(
|
const ThreeViewer = React.lazy(() => import(
|
||||||
/* webpackChunkName: "threeViewer" */
|
/* webpackChunkName: "threeViewer" */
|
||||||
|
@ -97,6 +119,8 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
renderViewer() {
|
renderViewer() {
|
||||||
const { source, mediaType, currentTheme, poster, claim } = this.props;
|
const { source, mediaType, currentTheme, poster, claim } = this.props;
|
||||||
|
|
||||||
|
console.log('mediaType', mediaType);
|
||||||
|
|
||||||
// Extract relevant data to render file
|
// Extract relevant data to render file
|
||||||
const { stream, fileType, contentType, downloadPath, fileName } = source;
|
const { stream, fileType, contentType, downloadPath, fileName } = source;
|
||||||
|
|
||||||
|
@ -123,7 +147,7 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
video: (
|
video: (
|
||||||
<AudioVideoViewer
|
<VideoViewer
|
||||||
claim={claim}
|
claim={claim}
|
||||||
source={{ downloadPath, fileName }}
|
source={{ downloadPath, fileName }}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
|
@ -131,7 +155,7 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
audio: (
|
audio: (
|
||||||
<AudioVideoViewer
|
<AudioViewer
|
||||||
claim={claim}
|
claim={claim}
|
||||||
source={{ downloadPath, fileName }}
|
source={{ downloadPath, fileName }}
|
||||||
contentType={contentType}
|
contentType={contentType}
|
||||||
|
@ -176,6 +200,7 @@ class FileRender extends React.PureComponent<Props> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
console.log('RENDER')
|
||||||
return (
|
return (
|
||||||
<div className="file-render">
|
<div className="file-render">
|
||||||
<React.Suspense fallback={<div></div>}>
|
<React.Suspense fallback={<div></div>}>
|
||||||
|
|
|
@ -47,11 +47,8 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
'comic-book',
|
'comic-book',
|
||||||
'document',
|
'document',
|
||||||
'3D-file',
|
'3D-file',
|
||||||
// The web can use the new video player, which has it's own file renderer
|
|
||||||
// @if TARGET='web'
|
|
||||||
'video',
|
'video',
|
||||||
'audio',
|
'audio',
|
||||||
// @endif
|
|
||||||
];
|
];
|
||||||
static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/';
|
static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/';
|
||||||
static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278';
|
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
|
// This files are supported using a custom viewer
|
||||||
const { mediaType, contentType } = this.props;
|
const { mediaType, contentType } = this.props;
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
mediaType,
|
||||||
|
contentType
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1 ||
|
MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1 ||
|
||||||
MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1
|
MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1
|
||||||
|
@ -360,6 +362,13 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
const isPlayableType = this.playableType();
|
const isPlayableType = this.playableType();
|
||||||
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
||||||
|
|
||||||
|
console.log({
|
||||||
|
mediaType,
|
||||||
|
fileSource,
|
||||||
|
isFileReady,
|
||||||
|
isFileType
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{loadingStatus && <LoadingScreen status={loadingStatus} spinner={isLoading} />}
|
{loadingStatus && <LoadingScreen status={loadingStatus} spinner={isLoading} />}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import Player from './internal/player';
|
|
||||||
import PlayButton from './internal/play-button';
|
import PlayButton from './internal/play-button';
|
||||||
|
|
||||||
|
const Player = React.lazy(() => import(
|
||||||
|
/* webpackChunkName: "player-legacy" */
|
||||||
|
'./internal/player'
|
||||||
|
));
|
||||||
|
|
||||||
const SPACE_BAR_KEYCODE = 32;
|
const SPACE_BAR_KEYCODE = 32;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -269,6 +273,7 @@ class FileViewer extends React.PureComponent<Props> {
|
||||||
<LoadingScreen status={loadStatusMessage} />
|
<LoadingScreen status={loadStatusMessage} />
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
<Suspense fallback={<div></div>}>
|
||||||
<Player
|
<Player
|
||||||
fileName={fileInfo.file_name}
|
fileName={fileInfo.file_name}
|
||||||
poster={poster}
|
poster={poster}
|
||||||
|
@ -288,6 +293,7 @@ class FileViewer extends React.PureComponent<Props> {
|
||||||
onFinishCb={this.onFileFinishCb}
|
onFinishCb={this.onFileFinishCb}
|
||||||
playingUri={playingUri}
|
playingUri={playingUri}
|
||||||
/>
|
/>
|
||||||
|
</Suspense>
|
||||||
)}
|
)}
|
||||||
</div>
|
</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;
|
const { theme, contentType } = me.props;
|
||||||
// Init CodeMirror
|
// Init CodeMirror
|
||||||
import(
|
import(
|
||||||
/* webpackChunkName: "codeViewer" */
|
/* webpackChunkName: "codemirror" */
|
||||||
'codemirror/lib/codemirror'
|
'codemirror/lib/codemirror'
|
||||||
).then((CodeMirror) => {
|
).then((CodeMirror) => {
|
||||||
me.codeMirror = CodeMirror.fromTextArea(me.textarea, {
|
me.codeMirror = CodeMirror.fromTextArea(me.textarea, {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import LoadingScreen from 'component/common/loading-screen';
|
import LoadingScreen from 'component/common/loading-screen';
|
||||||
import MarkdownPreview from 'component/common/markdown-preview';
|
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">
|
<div className="file-render__viewer document-viewer">
|
||||||
{loading && !error && <LoadingScreen status={loadingMessage} spinner />}
|
{loading && !error && <LoadingScreen status={loadingMessage} spinner />}
|
||||||
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
|
{error && <LoadingScreen status={errorMessage} spinner={!error} />}
|
||||||
{isReady && this.renderDocument()}
|
{isReady && <Suspense fallback={<div></div>}>{this.renderDocument()}</Suspense>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Claim } from 'types/claim';
|
import type { Claim } from 'types/claim';
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { stopContextMenu } from 'util/context-menu';
|
import { stopContextMenu } from 'util/context-menu';
|
||||||
import(/* webpackChunkName: "videojs" */
|
import(
|
||||||
|
/* webpackChunkName: "videojs" */
|
||||||
/* webpackPreload: true */
|
/* webpackPreload: true */
|
||||||
'video.js/dist/video-js.css');
|
'video.js/dist/video-js.css'
|
||||||
|
);
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
source: {
|
source: {
|
||||||
|
@ -39,11 +41,16 @@ class AudioVideoViewer extends React.PureComponent<Props> {
|
||||||
sources,
|
sources,
|
||||||
};
|
};
|
||||||
|
|
||||||
import(/* webpackChunkName: "videojs" */
|
import(
|
||||||
|
/* webpackChunkName: "videojs" */
|
||||||
/* webpackMode: "lazy" */
|
/* webpackMode: "lazy" */
|
||||||
/* webpackPreload: true */
|
/* webpackPreload: true */
|
||||||
'video.js').then(videojs => {
|
'video.js'
|
||||||
this.player = videojs.default(this.videoNode, videoJsOptions, () => {});
|
).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 LBRY = 'Lbry';
|
||||||
export const SEND = 'Send';
|
export const SEND = 'Send';
|
||||||
export const DISCOVER = 'Compass';
|
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 { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux';
|
||||||
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings';
|
||||||
import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc';
|
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 { store, history } from 'store';
|
||||||
import pjson from 'package.json';
|
import pjson from 'package.json';
|
||||||
import app from './app';
|
import app from './app';
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import { Modal } from 'modal/modal';
|
import { Modal } from 'modal/modal';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import UserPhoneNew from 'component/userPhoneNew';
|
|
||||||
import UserPhoneVerify from 'component/userPhoneVerify';
|
import UserPhoneVerify from 'component/userPhoneVerify';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
const LazyUserPhoneNew = React.lazy(() => import(
|
||||||
|
/* webpackChunkName: "userPhoneNew" */
|
||||||
|
'component/userPhoneNew'
|
||||||
|
));
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
phone: ?number,
|
phone: ?number,
|
||||||
user: {
|
user: {
|
||||||
|
@ -31,7 +35,11 @@ class ModalPhoneCollection extends React.PureComponent<Props> {
|
||||||
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
|
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
|
||||||
|
|
||||||
if (!user.phone_number && !phone) {
|
if (!user.phone_number && !phone) {
|
||||||
return <UserPhoneNew cancelButton={cancelButton} />;
|
return (
|
||||||
|
<Suspense fallback={<div></div>}>
|
||||||
|
<UserPhoneNew cancelButton={cancelButton} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
} else if (!user.phone_number) {
|
} else if (!user.phone_number) {
|
||||||
return <UserPhoneVerify cancelButton={cancelButton} />;
|
return <UserPhoneVerify cancelButton={cancelButton} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,6 +147,11 @@ class FilePage extends React.Component<Props> {
|
||||||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||||
const fileName = fileInfo ? fileInfo.file_name : null;
|
const fileName = fileInfo ? fileInfo.file_name : null;
|
||||||
const mediaType = getMediaType(contentType, fileName);
|
const mediaType = getMediaType(contentType, fileName);
|
||||||
|
console.log({
|
||||||
|
mediaType,
|
||||||
|
contentType,
|
||||||
|
fileName,
|
||||||
|
})
|
||||||
const showFile =
|
const showFile =
|
||||||
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
||||||
|
|
||||||
|
|
|
@ -24,8 +24,10 @@ import {
|
||||||
parseURI,
|
parseURI,
|
||||||
creditsToString,
|
creditsToString,
|
||||||
doError,
|
doError,
|
||||||
makeSelectCostInfoForUri,
|
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
|
import {
|
||||||
|
makeSelectCostInfoForUri,
|
||||||
|
} from 'lbryinc';
|
||||||
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
import { makeSelectClientSetting, selectosNotificationsEnabled } from 'redux/selectors/settings';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { formatLbryUriForWeb } from 'util/uri';
|
import { formatLbryUriForWeb } from 'util/uri';
|
||||||
|
|
|
@ -16,7 +16,6 @@ import { selectSubscriptions, selectUnreadByChannel } from 'redux/selectors/subs
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { Lbry, buildURI, parseURI, doResolveUris } from 'lbry-redux';
|
import { Lbry, buildURI, parseURI, doResolveUris } from 'lbry-redux';
|
||||||
import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content';
|
import { doPurchaseUri, doFetchClaimsByChannel } from 'redux/actions/content';
|
||||||
import Promise from 'bluebird';
|
|
||||||
|
|
||||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
||||||
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
||||||
|
|
|
@ -3,7 +3,6 @@ const webpack = require('webpack');
|
||||||
const merge = require('webpack-merge');
|
const merge = require('webpack-merge');
|
||||||
const { DefinePlugin, ProvidePlugin } = require('webpack');
|
const { DefinePlugin, ProvidePlugin } = require('webpack');
|
||||||
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
||||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
|
||||||
const TerserPlugin = require('terser-webpack-plugin');
|
const TerserPlugin = require('terser-webpack-plugin');
|
||||||
|
|
||||||
const NODE_ENV = process.env.NODE_ENV || 'development';
|
const NODE_ENV = process.env.NODE_ENV || 'development';
|
||||||
|
@ -41,11 +40,27 @@ let baseConfig = {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.s?css$/,
|
test: /\.module.scss$/,
|
||||||
use: [
|
use: [
|
||||||
'style-loader', // creates style nodes from JS strings
|
'style-loader',
|
||||||
'css-loader', // translates CSS into CommonJS
|
{
|
||||||
'sass-loader', // compiles Sass to CSS, using Node Sass by default
|
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: {
|
resolve: {
|
||||||
modules: [UI_ROOT, 'node_modules', __dirname],
|
modules: [UI_ROOT, 'node_modules', __dirname],
|
||||||
extensions: ['.js', '.jsx', '.json', '.scss'],
|
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: [
|
plugins: [
|
||||||
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
|
||||||
new webpack.EnvironmentPlugin(['NODE_ENV']),
|
new webpack.EnvironmentPlugin(['NODE_ENV']),
|
||||||
// new BundleAnalyzerPlugin(),
|
|
||||||
new ProvidePlugin({
|
new ProvidePlugin({
|
||||||
i18n: ['i18n', 'default'],
|
i18n: ['i18n', 'default'],
|
||||||
__: ['i18n/__', 'default'],
|
__: ['i18n/__', 'default'],
|
||||||
|
|
|
@ -5,6 +5,7 @@ const baseConfig = require('./webpack.base.config.js');
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
const { DefinePlugin } = require('webpack');
|
const { DefinePlugin } = require('webpack');
|
||||||
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
const { getIfUtils, removeEmpty } = require('webpack-config-utils');
|
||||||
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
|
|
||||||
const STATIC_ROOT = path.resolve(__dirname, 'static/');
|
const STATIC_ROOT = path.resolve(__dirname, 'static/');
|
||||||
const DIST_ROOT = path.resolve(__dirname, 'dist/');
|
const DIST_ROOT = path.resolve(__dirname, 'dist/');
|
||||||
|
@ -108,6 +109,7 @@ const renderConfig = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
// new BundleAnalyzerPlugin(),
|
||||||
new DefinePlugin({
|
new DefinePlugin({
|
||||||
IS_WEB: JSON.stringify(false),
|
IS_WEB: JSON.stringify(false),
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in a new issue