Use video.js for audio/video #1986

Closed
neb-b wants to merge 1 commit from videojs into master
7 changed files with 277 additions and 256 deletions

View file

@ -85,6 +85,7 @@
"stream-to-blob-url": "^2.1.1",
"three": "^0.93.0",
"tree-kill": "^1.1.0",
"video.js": "^7.2.2",
"y18n": "^4.0.0"
},
"devDependencies": {

View file

@ -5,13 +5,15 @@ import SemVer from 'semver';
import findProcess from 'find-process';
import url from 'url';
import https from 'https';
import { shell, app, ipcMain, dialog, session } from 'electron';
import { shell, app, ipcMain, dialog, session, protocol } from 'electron';
import { autoUpdater } from 'electron-updater';
import isDev from 'electron-is-dev';
import Daemon from './Daemon';
import createTray from './createTray';
import createWindow from './createWindow';
import pjson from '../../package.json';
import path from 'path';
import fs from 'fs';
autoUpdater.autoDownload = true;
@ -63,6 +65,8 @@ if (isDev) {
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
}
protocol.registerStandardSchemes(['content']);
app.on('ready', async () => {
const processList = await findProcess('name', 'lbrynet-daemon');
const isDaemonRunning = processList.length > 0;
@ -84,22 +88,48 @@ app.on('ready', async () => {
});
daemon.launch();
}
// https://electronjs.org/docs/api/protocol#protocolregisterstreamprotocolscheme-handler-completion
// protocol.registerStreamProtocol(
// 'content',
// (request, callback) => {
// console.log('response', request);
// const filePath = request.url.slice('content://'.length);
//
// callback({
// statusCode: 209,
// data: fs.createReadStream(filePath),
// });
// },
// error => {
// if (error) console.error('err', err);
// if (!error) console.log('success');
// }
// );
if (isDev) {
await installExtensions();
}
rendererWindow = createWindow(appState);
rendererWindow.webContents.on('devtools-opened', () => {
rendererWindow.webContents.send('devtools-is-opened');
});
tray = createTray(rendererWindow);
// HACK: patch webrequest to fix devtools incompatibility with electron 2.x.
// See https://github.com/electron/electron/issues/13008#issuecomment-400261941
session.defaultSession.webRequest.onBeforeRequest({}, (details, callback) => {
if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') !== -1) {
callback({redirectURL: details.url.replace('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33', '57c9d07b416b5a2ea23d28247300e4af36329bdc')});
} else {
callback({cancel: false});
}
if (details.url.indexOf('7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33') !== -1) {
callback({
redirectURL: details.url.replace(
'7accc8730b0f99b5e7c0702ea89d1fa7c17bfe33',
'57c9d07b416b5a2ea23d28247300e4af36329bdc'
),
});
} else {
callback({ cancel: false });
}
});
});

View file

@ -6,6 +6,7 @@ import ThreeViewer from 'component/viewers/threeViewer';
import DocumentViewer from 'component/viewers/documentViewer';
import DocxViewer from 'component/viewers/docxViewer';
import HtmlViewer from 'component/viewers/htmlViewer';
import AudioVideoViewer from 'component/viewers/audioVideoViewer';
type Props = {
mediaType: string,
@ -21,7 +22,7 @@ type Props = {
class FileRender extends React.PureComponent<Props> {
renderViewer() {
const { source, mediaType, currentTheme } = this.props;
const { source, mediaType, currentTheme, poster } = this.props;
// Extract relevant data to render file
const { blob, stream, fileName, fileType, contentType, downloadPath } = source;
@ -32,6 +33,15 @@ class FileRender extends React.PureComponent<Props> {
// Supported mediaTypes
const mediaTypes = {
'3D-file': <ThreeViewer source={{ fileType, downloadPath }} theme={currentTheme} />,
video: (
<AudioVideoViewer
source={{ downloadPath, fileName }}
contentType={contentType}
poster={poster}
/>
),
audio: <AudioVideoViewer source={{ downloadPath, fileName }} contentType={contentType} />,
// Add routes to viewer...
};

View file

@ -1,4 +1,3 @@
/* eslint-disable */
import React from 'react';
import { remote } from 'electron';
import fs from 'fs';
@ -9,9 +8,26 @@ import FileRender from 'component/fileRender';
import Thumbnail from 'component/common/thumbnail';
import LoadingScreen from 'component/common/loading-screen';
// Handle fullscreen change for the Windows platform
const win32FullScreenChange = () => {
const win = remote.BrowserWindow.getFocusedWindow();
if (process.platform === 'win32') {
win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu());
}
};
class MediaPlayer extends React.PureComponent {
static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg'];
static FILE_MEDIA_TYPES = ['text', 'script', 'e-book', 'comic-book', 'document', '3D-file'];
// static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg'];
static FILE_MEDIA_TYPES = [
'video',
'audio',
'text',
'script',
'e-book',
'comic-book',
'document',
'3D-file',
];
constructor(props) {
super(props);
@ -23,189 +39,40 @@ class MediaPlayer extends React.PureComponent {
fileSource: null,
};
this.togglePlayListener = this.togglePlay.bind(this);
this.toggleFullScreenVideo = this.toggleFullScreen.bind(this);
}
componentDidUpdate(nextProps) {
const el = this.refs.media.children[0];
if (this.props.playingUri && !nextProps.playingUri && !el.paused) {
el.pause();
}
// this.togglePlayListener = this.togglePlay.bind(this);
// this.toggleFullScreenVideo = this.toggleFullScreen.bind(this);
}
componentDidMount() {
const container = this.media;
const {
downloadCompleted,
contentType,
changeVolume,
volume,
position,
claim,
startedPlayingCb,
} = this.props;
const loadedMetadata = () => {
this.setState({ hasMetadata: true, startedPlaying: true });
if (startedPlayingCb) {
startedPlayingCb();
}
this.media.children[0].play();
};
const renderMediaCallback = error => {
if (error) this.setState({ unplayable: true });
};
// Handle fullscreen change for the Windows platform
const win32FullScreenChange = () => {
const win = remote.BrowserWindow.getFocusedWindow();
if (process.platform === 'win32') {
win.setMenu(document.webkitIsFullScreen ? null : remote.Menu.getApplicationMenu());
}
};
// use renderAudio override for mp3
if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(container, null, false);
}
// Render custom viewer: FileRender
else if (this.fileType()) {
downloadCompleted && this.renderFile();
}
// Render default viewer: render-media (video, audio, img, iframe)
else {
player.append(
this.file(),
container,
{ autoplay: true, controls: true },
renderMediaCallback.bind(this)
);
}
document.addEventListener('keydown', this.togglePlayListener);
const mediaElement = this.media.children[0];
if (mediaElement) {
if (position) {
mediaElement.currentTime = position;
}
mediaElement.addEventListener('timeupdate', () =>
this.props.savePosition(
claim.claim_id,
`${claim.txid}:${claim.nout}`,
mediaElement.currentTime
)
);
mediaElement.addEventListener('click', this.togglePlayListener);
mediaElement.addEventListener('loadedmetadata', loadedMetadata.bind(this), {
once: true,
});
mediaElement.addEventListener('webkitfullscreenchange', win32FullScreenChange.bind(this));
mediaElement.addEventListener('volumechange', () => {
changeVolume(mediaElement.volume);
});
mediaElement.volume = volume;
mediaElement.addEventListener('dblclick', this.toggleFullScreenVideo);
}
}
componentWillReceiveProps(next) {
const el = this.media.children[0];
if (!this.props.paused && next.paused && !el.paused) el.pause();
}
componentDidUpdate() {
const { contentType, downloadCompleted } = this.props;
const { startedPlaying, fileSource } = this.state;
if (this.playableType() && !startedPlaying && downloadCompleted) {
const container = this.media.children[0];
if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
this.renderAudio(this.media, true);
} else {
player.render(this.file(), container, {
autoplay: true,
controls: true,
});
}
} else if (this.fileType() && !fileSource && downloadCompleted) {
if (this.isSupportedFile()) {
this.renderFile();
}
}
componentWillUnmount() {
document.removeEventListener('keydown', this.togglePlayListener);
const mediaElement = this.media.children[0];
if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlayListener);
componentDidUpdate() {
const { fileName, downloadPath, contentType } = this.props;
const { fileSource } = this.state;
if (!fileSource && fileName && downloadPath && contentType) {
this.renderFile();
}
}
toggleFullScreen(event) {
const mediaElement = this.media.children[0];
if (mediaElement) {
if (document.webkitIsFullScreen) {
document.webkitExitFullscreen();
} else {
mediaElement.webkitRequestFullScreen();
}
}
}
togglePlay(event) {
// ignore all events except click and spacebar keydown, or input events in a form control
if (
event.type === 'keydown' &&
(event.code !== 'Space' || event.target.tagName.toLowerCase() === 'input')
) {
return;
}
event.preventDefault();
const mediaElement = this.media.children[0];
if (mediaElement) {
if (!mediaElement.paused) {
mediaElement.pause();
} else {
mediaElement.play();
}
}
}
file() {
const { downloadPath, fileName } = this.props;
return {
name: fileName,
createReadStream: opts => fs.createReadStream(downloadPath, opts),
};
}
playableType() {
const { mediaType } = this.props;
return ['audio', 'video'].indexOf(mediaType) !== -1;
}
supportedType() {
// Files supported by render-media
const { contentType, mediaType } = this.props;
return Object.values(player.mime).indexOf(contentType) !== -1;
}
fileType() {
isSupportedFile() {
// This files are supported using a custom viewer
const { mediaType } = this.props;
return MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1;
const isSupported = MediaPlayer.FILE_MEDIA_TYPES.includes(mediaType);
return isSupported;
}
renderFile() {
// This is what render-media does with unplayable files
const { fileName, downloadPath, contentType, mediaType } = this.props;
// We know we can render this file
// Set the fileSource to state so the FileRender component will be rendered
const { fileName, downloadPath, contentType } = this.props;
if (!fileName || !downloadPath || !contentType) {
return;
}
// File to render
const fileSource = {
@ -213,96 +80,27 @@ class MediaPlayer extends React.PureComponent {
contentType,
downloadPath,
fileType: path.extname(fileName).substring(1),
// // Readable stream from file
// stream: opts => fs.createReadStream(downloadPath, opts),
};
// Readable stream from file
fileSource.stream = opts => fs.createReadStream(downloadPath, opts);
// Blob url from stream
fileSource.blob = callback =>
toBlobURL(fs.createReadStream(downloadPath), contentType, callback);
// Update state
this.setState({ fileSource });
}
renderAudio(container, autoplay) {
if (container.firstChild) {
container.firstChild.remove();
}
// clear the container
const { downloadPath } = this.props;
const audio = document.createElement('audio');
audio.autoplay = autoplay;
audio.controls = true;
audio.src = downloadPath;
container.appendChild(audio);
}
showLoadingScreen(isFileType, isPlayableType) {
const { mediaType } = this.props;
const { hasMetadata, unplayable, unsupported, fileSource } = this.state;
const loader = {
isLoading: false,
loadingStatus: null,
};
// Loading message
const noFileMessage = __('Waiting for blob.');
const noMetadataMessage = __('Waiting for metadata.');
// Error message
const unplayableMessage = __("Sorry, looks like we can't play this file.");
const unsupportedMessage = __("Sorry, looks like we can't preview this file.");
// Files
const isLoadingFile = !fileSource && isFileType;
const isUnsupported =
(mediaType === 'application' || !this.supportedType()) && !isFileType && !isPlayableType;
// Media (audio, video)
const isUnplayable = isPlayableType && unplayable;
const isLoadingMetadata = isPlayableType && (!hasMetadata && !unplayable);
// Show loading message
if (isLoadingFile || isLoadingMetadata) {
loader.loadingStatus = isFileType ? noFileMessage : noMetadataMessage;
loader.isLoading = true;
// Show unsupported error message
} else if (isUnsupported || isUnplayable) {
loader.loadingStatus = isUnsupported ? unsupportedMessage : unplayableMessage;
}
return loader;
}
render() {
const { mediaType } = this.props;
const { mediaType, poster } = this.props;
const { fileSource } = this.state;
const isFileType = this.fileType();
const isFileType = this.isSupportedFile();
const isFileReady = fileSource && isFileType;
const isPlayableType = this.playableType();
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
return (
<React.Fragment>
{loadingStatus && <LoadingScreen status={loadingStatus} spinner={isLoading} />}
{isFileReady && <FileRender source={fileSource} mediaType={mediaType} />}
<div
className={'content__view--container'}
style={{ opacity: isLoading ? 0 : 1 }}
ref={container => {
this.media = container;
}}
/>
{!isFileReady && <LoadingScreen status="loadingStatus" spinner />}
{isFileReady && <FileRender source={fileSource} mediaType={mediaType} poster={poster} />}
</React.Fragment>
);
}
}
export default MediaPlayer;
/* eslint-disable */

View file

@ -0,0 +1,80 @@
// @flow
import React from 'react';
import { stopContextMenu } from 'util/contextMenu';
import videojs from 'video.js';
import 'video.js/dist/video-js.css';
import toBlobURL from 'stream-to-blob-url';
import fs from 'fs';
type Props = {
source: {
downloadPath: string,
fileName: string,
},
contentType: string,
poster?: string,
};
class AudioVideoViewer extends React.PureComponent<Props> {
componentDidMount() {
const { source, contentType, poster } = this.props;
const { downloadPath, fileName } = source;
const indexOfFileName = downloadPath.indexOf(fileName);
const basePath = downloadPath.slice(0, indexOfFileName);
const encodedFileName = encodeURIComponent(fileName);
// We only want to encode the fileName so forward slashes "/" are handled properly by the file system
// TODO: Determine changes needed for windows
const path = `file://${basePath}${encodedFileName}`;
// Another alternative, maybe we don't need to do anything in the main electron process?
// get blob url, then set as source and call videojs()
// toBlobURL(fs.createReadStream(downloadPath), (err, url) => {
// if (err) return console.error(err.message)
// console.log(url);
// const sources = [
// {
// src: url,
// type: contentType
// }
// ]
const sources = [
{
src: path,
type: contentType,
},
];
const videoJsOptions = {
autoplay: true,
controls: true,
preload: 'auto',
poster,
sources,
};
this.player = videojs(this.videoNode, videoJsOptions, () => {});
// })
}
componentWillUnmount() {
if (this.player) {
this.player.dispose();
}
}
render() {
const { source } = this.props;
return (
<div className="file-render__viewer" onContextMenu={stopContextMenu}>
<div data-vjs-player>
<video ref={node => (this.videoNode = node)} className="video-js" />
</div>
</div>
);
}
}
export default AudioVideoViewer;

View file

@ -18,10 +18,17 @@
background-color: black;
iframe,
webview {
webview,
.video-js {
width: 100%;
height: 100%;
}
// Removing the play button because we have autoplay turned on
// These are classes added by video.js
.video-js .vjs-big-play-button {
display: none;
}
}
.document-viewer {

View file

@ -134,6 +134,18 @@
version "1.13.6"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"
"@videojs/http-streaming@1.2.4":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@videojs/http-streaming/-/http-streaming-1.2.4.tgz#6245524b76203db5e6750153d4896d007cc7f7cd"
dependencies:
aes-decrypter "3.0.0"
global "^4.3.0"
m3u8-parser "4.2.0"
mpd-parser "0.6.1"
mux.js "4.5.1"
url-toolkit "^2.1.3"
video.js "^6.8.0 || ^7.0.0"
abbrev@1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
@ -173,6 +185,14 @@ acorn@^5.0.0, acorn@^5.2.1, acorn@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.1.tgz#f095829297706a7c9776958c0afc8930a9b9d9d8"
aes-decrypter@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/aes-decrypter/-/aes-decrypter-3.0.0.tgz#7848a1c145b9fdbf57ae3e2b5b1bc7cf0644a8fb"
dependencies:
commander "^2.9.0"
global "^4.3.2"
pkcs7 "^1.0.2"
agent-base@4, agent-base@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
@ -1134,7 +1154,7 @@ babel-register@^6.26.0:
mkdirp "^0.5.1"
source-map-support "^0.4.15"
babel-runtime@6.x, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0:
babel-runtime@6.x, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0, babel-runtime@^6.9.2:
version "6.26.0"
resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe"
dependencies:
@ -4400,7 +4420,7 @@ global-dirs@^0.1.0:
dependencies:
ini "^1.3.4"
global@~4.3.0:
global@4.3.2, global@^4.3.0, global@^4.3.1, global@^4.3.2, global@~4.3.0:
version "4.3.2"
resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f"
dependencies:
@ -4947,6 +4967,10 @@ indexof@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
individual@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/individual/-/individual-2.0.0.tgz#833b097dad23294e76117a98fb38e0d9ad61bb97"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@ -6051,6 +6075,10 @@ lz-string@^1.4.4:
version "1.4.4"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
m3u8-parser@4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/m3u8-parser/-/m3u8-parser-4.2.0.tgz#c8e0785fd17f741f4408b49466889274a9e36447"
make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
@ -6447,6 +6475,13 @@ mp4-stream@^2.0.0:
next-event "^1.0.0"
readable-stream "^2.0.3"
mpd-parser@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/mpd-parser/-/mpd-parser-0.6.1.tgz#27e7aafe075817846ce55406ac03711df1ce0eb7"
dependencies:
global "^4.3.0"
url-toolkit "^2.1.1"
ms@0.7.1:
version "0.7.1"
resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098"
@ -6477,6 +6512,10 @@ mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
mux.js@4.5.1:
version "4.5.1"
resolved "https://registry.yarnpkg.com/mux.js/-/mux.js-4.5.1.tgz#1d70f1ad9b951315e16390d47be8fc42fd080194"
nan@2.8.0:
version "2.8.0"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
@ -7231,6 +7270,10 @@ pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
pkcs7@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pkcs7/-/pkcs7-1.0.2.tgz#b6dba527528c2942bfc122ce2dafcdb5e59074e7"
pkg-dir@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4"
@ -8425,6 +8468,12 @@ run-queue@^1.0.0, run-queue@^1.0.3:
dependencies:
aproba "^1.1.1"
rust-result@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/rust-result/-/rust-result-1.0.0.tgz#34c75b2e6dc39fe5875e5bdec85b5e0f91536f72"
dependencies:
individual "^2.0.0"
rx-lite-aggregates@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be"
@ -8449,6 +8498,12 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2,
version "5.1.2"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
safe-json-parse@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-4.0.0.tgz#7c0f578cfccd12d33a71c0e05413e2eca171eaac"
dependencies:
rust-result "^1.0.0"
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@ -9518,6 +9573,10 @@ tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
tsml@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tsml/-/tsml-1.0.1.tgz#89f8218b9d9e257f47d7f6b56d01c5a4d2c68fc3"
tty-browserify@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@ -9846,6 +9905,10 @@ 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:
version "2.1.6"
resolved "https://registry.yarnpkg.com/url-toolkit/-/url-toolkit-2.1.6.tgz#6d03246499e519aad224c44044a4ae20544154f2"
url@0.10.3:
version "0.10.3"
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
@ -9946,6 +10009,29 @@ vfile@^2.0.0:
unist-util-stringify-position "^1.0.0"
vfile-message "^1.0.0"
"video.js@^6.8.0 || ^7.0.0", video.js@^7.2.2:
version "7.2.2"
resolved "https://registry.yarnpkg.com/video.js/-/video.js-7.2.2.tgz#4a1197b2f0c265a1e50d5cd4ff41cd030e22a423"
dependencies:
"@videojs/http-streaming" "1.2.4"
babel-runtime "^6.9.2"
global "4.3.2"
safe-json-parse "4.0.0"
tsml "1.0.1"
videojs-font "3.0.0"
videojs-vtt.js "0.14.1"
xhr "2.4.0"
videojs-font@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/videojs-font/-/videojs-font-3.0.0.tgz#90eafddcf26b407448c833523f5ca4ad8d5cc1af"
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"
dependencies:
global "^4.3.1"
videostream@^2.3.0:
version "2.4.3"
resolved "https://registry.yarnpkg.com/videostream/-/videostream-2.4.3.tgz#bdcc252309fa1d4e7077643d2809b822270b5e60"
@ -10248,6 +10334,15 @@ xdg-basedir@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
xhr@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.4.0.tgz#e16e66a45f869861eeefab416d5eff722dc40993"
dependencies:
global "~4.3.0"
is-function "^1.0.1"
parse-headers "^2.0.0"
xtend "^4.0.0"
xhr@^2.0.1:
version "2.5.0"
resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd"