Add v1.1.0
This commit is contained in:
parent
110a36a60f
commit
0bc412c633
@ -3,6 +3,7 @@ Dockerfile*
|
|||||||
.editorconfig
|
.editorconfig
|
||||||
.gitignore
|
.gitignore
|
||||||
README.md
|
README.md
|
||||||
|
CHANGELOG.md
|
||||||
node_modules/
|
node_modules/
|
||||||
.yarn/cache
|
.yarn/cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|||||||
26
.linguirc
26
.linguirc
@ -1,10 +1,24 @@
|
|||||||
{
|
{
|
||||||
"catalogs": [{
|
"catalogs": [
|
||||||
"path": "src/locales/{locale}/messages",
|
{
|
||||||
"include": ["src/"],
|
"path": "src/locales/{locale}/messages",
|
||||||
"exclude": ["**/node_modules/**"]
|
"include": [
|
||||||
}],
|
"src/"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"**/node_modules/**"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"format": "po",
|
"format": "po",
|
||||||
"sourceLocale": "en",
|
"sourceLocale": "en",
|
||||||
"locales": ["en", "de", "fr", "it", "pt", "es"]
|
"locales": [
|
||||||
|
"en",
|
||||||
|
"de",
|
||||||
|
"fr",
|
||||||
|
"it",
|
||||||
|
"pt",
|
||||||
|
"es",
|
||||||
|
"ru"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
28
CHANGELOG.md
Normal file
28
CHANGELOG.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Restreamer-UI
|
||||||
|
|
||||||
|
#### v1.0.0 > v1.1.0
|
||||||
|
|
||||||
|
- Add "HLS cleanup" as an optional function ([Philipp Trenz](https://github.com/philipptrenz))
|
||||||
|
- Add /ui info to / ([#326](https://github.com/datarhei/restreamer/issues/326))
|
||||||
|
- Add Russian translation (thx Inthegamelp)
|
||||||
|
- Add missed VAAPI encoder
|
||||||
|
- Add missed V4L2_M2M encoder
|
||||||
|
- Add missed Raspberry Pi 64bit Docker image
|
||||||
|
- Mod updates VideoJS
|
||||||
|
- Add option to disable playersites share-button (thx Anders Mellgren)
|
||||||
|
- Fix hides unset content license on playersite (thx Anders Mellgren)
|
||||||
|
- Fix updates V4L2 device-list on select
|
||||||
|
- Fix snapshot interval ([#341](https://github.com/datarhei/restreamer/issues/340))
|
||||||
|
- Fix reverse proxy issue ([#340](https://github.com/datarhei/restreamer/issues/340))
|
||||||
|
- Fix double escape failer ([#336](https://github.com/datarhei/restreamer/issues/336))
|
||||||
|
- Fix type in player plugin ([#336](https://github.com/datarhei/restreamer/issues/336))
|
||||||
|
- Fix deletes processes with dependencies (thx Patron Ramakrishna Chillara)
|
||||||
|
- Fix datarhei Core publication service
|
||||||
|
- Fix dependabot alerts
|
||||||
|
- Fix code scanning alerts
|
||||||
|
- Merge security pr
|
||||||
|
|
||||||
|
Preparation for FFmpeg v5.0 (migration will not work)
|
||||||
|
|
||||||
|
- Add FFmpeg v5.0 commands (preparation)
|
||||||
|
- Mod allows FFmpeg v5.0 (preparation)
|
||||||
18
Dockerfile
18
Dockerfile
@ -1,11 +1,23 @@
|
|||||||
FROM node:17-alpine3.15
|
FROM node:17.9.0-alpine3.15
|
||||||
|
|
||||||
WORKDIR /ui
|
ARG NODE_SPACE_SIZE=10240
|
||||||
|
ENV NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=$NODE_SPACE_SIZE"
|
||||||
|
|
||||||
|
ENV PUBLIC_URL "/"
|
||||||
|
|
||||||
COPY . /ui
|
COPY . /ui
|
||||||
|
|
||||||
RUN yarn install && \
|
WORKDIR /ui
|
||||||
|
|
||||||
|
RUN cd /ui && \
|
||||||
|
npm config set fetch-retries 10 && \
|
||||||
|
npm config set fetch-retry-mintimeout 100000 && \
|
||||||
|
npm config set fetch-retry-maxtimeout 600000 && \
|
||||||
|
npm config set cache-min 3600 && \
|
||||||
|
npm config ls -l && \
|
||||||
|
npm install && \
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD [ "npm", "run", "start" ]
|
CMD [ "npm", "run", "start" ]
|
||||||
|
|||||||
4
LICENSE
4
LICENSE
@ -178,7 +178,7 @@
|
|||||||
APPENDIX: How to apply the Apache License to your work.
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
To apply the Apache License to your work, attach the following
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
replaced with your own identifying information. (Don't include
|
replaced with your own identifying information. (Don't include
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
comment syntax for the file format. We also recommend that a
|
comment syntax for the file format. We also recommend that a
|
||||||
@ -186,7 +186,7 @@
|
|||||||
same "printed page" as the copyright notice for easier
|
same "printed page" as the copyright notice for easier
|
||||||
identification within third-party archives.
|
identification within third-party archives.
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
Copyright 2022 FOSS GmbH
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
68
package.json
68
package.json
@ -1,39 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "restreamer-ui",
|
"name": "restreamer-ui",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"bundle": "restreamer-v2.0.0",
|
"bundle": "restreamer-v2.1.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth0/auth0-spa-js": "^1.21.1",
|
"@auth0/auth0-spa-js": "^1.22.0",
|
||||||
"@clappr/core": "^0.4.17",
|
"@clappr/core": "^0.4.21",
|
||||||
"@clappr/hlsjs-playback": "^0.5.3",
|
"@clappr/hlsjs-playback": "^0.6.0",
|
||||||
"@clappr/plugins": "^0.4.10",
|
"@clappr/plugins": "^0.4.16",
|
||||||
"@clappr/stats-plugin": "^0.2.0",
|
"@clappr/stats-plugin": "^0.2.0",
|
||||||
"@emotion/react": "^11.5.0",
|
"@emotion/react": "^11.9.0",
|
||||||
"@emotion/styled": "^11.3.0",
|
"@emotion/styled": "^11.8.1",
|
||||||
"@fontsource/dosis": "^4.5.1",
|
"@fontsource/dosis": "^4.5.8",
|
||||||
"@fontsource/roboto": "^4.5.5",
|
"@fontsource/roboto": "^4.5.7",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||||
"@fortawesome/free-brands-svg-icons": "^5.15.2",
|
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^5.15.2",
|
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.14",
|
"@fortawesome/react-fontawesome": "^0.1.18",
|
||||||
"@lingui/core": "^3.13.2",
|
"@lingui/core": "^3.13.3",
|
||||||
"@lingui/macro": "^3.4.0",
|
"@lingui/macro": "^3.13.3",
|
||||||
"@lingui/react": "^3.4.0",
|
"@lingui/react": "^3.13.3",
|
||||||
"@material-ui/core": "^4.11.3",
|
"@mui/icons-material": "^5.8.2",
|
||||||
"@material-ui/icons": "^4.11.2",
|
"@mui/lab": "^5.0.0-alpha.84",
|
||||||
"@material-ui/lab": "^4.0.0-alpha.57",
|
"@mui/material": "5.1.1",
|
||||||
"@mui/icons-material": "^5.0.4",
|
"@mui/styles": "^5.1.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.51",
|
"@testing-library/dom": "^8.13.0",
|
||||||
"@mui/material": "^5.0.4",
|
|
||||||
"@mui/styles": "^5.7.0",
|
|
||||||
"@testing-library/dom": ">=5",
|
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/jest-dom": "^4.2.4",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@testing-library/react": "^12.1.5",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@testing-library/user-event": "^13.5.0",
|
||||||
"babel-plugin-macros": "2 || 3",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"eslint": "^7.19.0",
|
"eslint": "^7.32.0",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "^4.7.7",
|
||||||
"hls.js": "^0.14.17",
|
"hls.js": "^0.14.17",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"make-plural": "^7.1.0",
|
"make-plural": "^7.1.0",
|
||||||
@ -41,9 +39,9 @@
|
|||||||
"react-colorful": "^5.5.1",
|
"react-colorful": "^5.5.1",
|
||||||
"react-device-detect": "^2.2.2",
|
"react-device-detect": "^2.2.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
"react-router-dom": "^6.2.1",
|
"react-router-dom": "^6.3.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "^4.0.3",
|
||||||
"semver": "^7.3.4",
|
"semver": "^7.3.7",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.7",
|
||||||
"url-parse": "^1.5.10",
|
"url-parse": "^1.5.10",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
@ -84,10 +82,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.12.10",
|
"@babel/core": "^7.18.2",
|
||||||
"@lingui/cli": "^3.4.0",
|
"@lingui/cli": "^3.13.3",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
"prettier": "2.2.1",
|
"prettier": "^2.6.2",
|
||||||
"react-error-overlay": "^6.0.11"
|
"react-error-overlay": "^6.0.11"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
|
|||||||
17
public/_player/videojs/dist/video-js.css
vendored
17
public/_player/videojs/dist/video-js.css
vendored
@ -384,26 +384,33 @@
|
|||||||
.video-js.vjs-1-1 {
|
.video-js.vjs-1-1 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js.vjs-fluid:not(.vjs-audio-only-mode),
|
||||||
|
.video-js.vjs-16-9:not(.vjs-audio-only-mode),
|
||||||
|
.video-js.vjs-4-3:not(.vjs-audio-only-mode),
|
||||||
|
.video-js.vjs-9-16:not(.vjs-audio-only-mode),
|
||||||
|
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-16-9 {
|
.video-js.vjs-16-9:not(.vjs-audio-only-mode) {
|
||||||
padding-top: 56.25%;
|
padding-top: 56.25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-4-3 {
|
.video-js.vjs-4-3:not(.vjs-audio-only-mode) {
|
||||||
padding-top: 75%;
|
padding-top: 75%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-9-16 {
|
.video-js.vjs-9-16:not(.vjs-audio-only-mode) {
|
||||||
padding-top: 177.7777777778%;
|
padding-top: 177.7777777778%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-1-1 {
|
.video-js.vjs-1-1:not(.vjs-audio-only-mode) {
|
||||||
padding-top: 100%;
|
padding-top: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js.vjs-fill {
|
.video-js.vjs-fill:not(.vjs-audio-only-mode) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
2
public/_player/videojs/dist/video-js.min.css
vendored
2
public/_player/videojs/dist/video-js.min.css
vendored
File diff suppressed because one or more lines are too long
458
public/_player/videojs/dist/video.js
vendored
458
public/_player/videojs/dist/video.js
vendored
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @license
|
* @license
|
||||||
* Video.js 7.19.0 <http://videojs.com/>
|
* Video.js 7.19.2 <http://videojs.com/>
|
||||||
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
* Copyright Brightcove, Inc. <https://www.brightcove.com/>
|
||||||
* Available under Apache License Version 2.0
|
* Available under Apache License Version 2.0
|
||||||
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
* <https://github.com/videojs/video.js/blob/main/LICENSE>
|
||||||
@ -16,7 +16,7 @@
|
|||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojs = factory());
|
||||||
}(this, (function () { 'use strict';
|
}(this, (function () { 'use strict';
|
||||||
|
|
||||||
var version$5 = "7.19.0";
|
var version$5 = "7.19.2";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An Object that contains lifecycle hooks as keys which point to an array
|
* An Object that contains lifecycle hooks as keys which point to an array
|
||||||
@ -18587,8 +18587,10 @@
|
|||||||
|
|
||||||
if (this.items && this.items.length <= this.hideThreshold_) {
|
if (this.items && this.items.length <= this.hideThreshold_) {
|
||||||
this.hide();
|
this.hide();
|
||||||
|
this.menu.contentEl_.removeAttribute('role');
|
||||||
} else {
|
} else {
|
||||||
this.show();
|
this.show();
|
||||||
|
this.menu.contentEl_.setAttribute('role', 'menu');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -25588,7 +25590,7 @@
|
|||||||
|
|
||||||
|
|
||||||
this.addClass(idClass);
|
this.addClass(idClass);
|
||||||
setTextContent(this.styleEl_, "\n ." + idClass + " {\n width: " + width + "px;\n height: " + height + "px;\n }\n\n ." + idClass + ".vjs-fluid {\n padding-top: " + ratioMultiplier * 100 + "%;\n }\n ");
|
setTextContent(this.styleEl_, "\n ." + idClass + " {\n width: " + width + "px;\n height: " + height + "px;\n }\n\n ." + idClass + ".vjs-fluid:not(.vjs-audio-only-mode) {\n padding-top: " + ratioMultiplier * 100 + "%;\n }\n ");
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Load/Create an instance of playback {@link Tech} including element
|
* Load/Create an instance of playback {@link Tech} including element
|
||||||
@ -30889,7 +30891,7 @@
|
|||||||
head.insertBefore(style, head.firstChild);
|
head.insertBefore(style, head.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid {\n padding-top: 56.25%\n }\n ");
|
setTextContent(style, "\n .video-js {\n width: 300px;\n height: 150px;\n }\n\n .vjs-fluid:not(.vjs-audio-only-mode) {\n padding-top: 56.25%\n }\n ");
|
||||||
}
|
}
|
||||||
} // Run Auto-load players
|
} // Run Auto-load players
|
||||||
// You have to wait at least once in case this script is loaded after your
|
// You have to wait at least once in case this script is loaded after your
|
||||||
@ -31515,7 +31517,7 @@
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! @name m3u8-parser @version 4.7.0 @license Apache-2.0 */
|
/*! @name m3u8-parser @version 4.7.1 @license Apache-2.0 */
|
||||||
/**
|
/**
|
||||||
* A stream that buffers string input and generates a `data` event for each
|
* A stream that buffers string input and generates a `data` event for each
|
||||||
* line.
|
* line.
|
||||||
@ -32577,6 +32579,15 @@
|
|||||||
attributes: entry.attributes
|
attributes: entry.attributes
|
||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
|
||||||
|
this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
|
||||||
|
|
||||||
|
this.manifest.contentProtection['com.microsoft.playready'] = {
|
||||||
|
uri: entry.attributes.URI
|
||||||
|
};
|
||||||
|
return;
|
||||||
} // check if the content is encrypted for Widevine
|
} // check if the content is encrypted for Widevine
|
||||||
// Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
|
// Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
|
||||||
|
|
||||||
@ -33290,6 +33301,193 @@
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
|
||||||
|
// we used to do this with log2 but BigInt does not support builtin math
|
||||||
|
// Math.ceil(log2(x));
|
||||||
|
|
||||||
|
|
||||||
|
var countBits = function countBits(x) {
|
||||||
|
return x.toString(2).length;
|
||||||
|
}; // count the number of whole bytes it would take to represent a number
|
||||||
|
|
||||||
|
var countBytes = function countBytes(x) {
|
||||||
|
return Math.ceil(countBits(x) / 8);
|
||||||
|
};
|
||||||
|
var isArrayBufferView = function isArrayBufferView(obj) {
|
||||||
|
if (ArrayBuffer.isView === 'function') {
|
||||||
|
return ArrayBuffer.isView(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj && obj.buffer instanceof ArrayBuffer;
|
||||||
|
};
|
||||||
|
var isTypedArray = function isTypedArray(obj) {
|
||||||
|
return isArrayBufferView(obj);
|
||||||
|
};
|
||||||
|
var toUint8 = function toUint8(bytes) {
|
||||||
|
if (bytes instanceof Uint8Array) {
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
|
||||||
|
// any non-number or NaN leads to empty uint8array
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
|
||||||
|
bytes = 0;
|
||||||
|
} else {
|
||||||
|
bytes = [bytes];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
|
||||||
|
};
|
||||||
|
var BigInt = window.BigInt || Number;
|
||||||
|
var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
|
||||||
|
var bytesToNumber = function bytesToNumber(bytes, _temp) {
|
||||||
|
var _ref = _temp === void 0 ? {} : _temp,
|
||||||
|
_ref$signed = _ref.signed,
|
||||||
|
signed = _ref$signed === void 0 ? false : _ref$signed,
|
||||||
|
_ref$le = _ref.le,
|
||||||
|
le = _ref$le === void 0 ? false : _ref$le;
|
||||||
|
|
||||||
|
bytes = toUint8(bytes);
|
||||||
|
var fn = le ? 'reduce' : 'reduceRight';
|
||||||
|
var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
|
||||||
|
var number = obj.call(bytes, function (total, _byte, i) {
|
||||||
|
var exponent = le ? i : Math.abs(i + 1 - bytes.length);
|
||||||
|
return total + BigInt(_byte) * BYTE_TABLE[exponent];
|
||||||
|
}, BigInt(0));
|
||||||
|
|
||||||
|
if (signed) {
|
||||||
|
var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
|
||||||
|
number = BigInt(number);
|
||||||
|
|
||||||
|
if (number > max) {
|
||||||
|
number -= max;
|
||||||
|
number -= max;
|
||||||
|
number -= BigInt(2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number(number);
|
||||||
|
};
|
||||||
|
var numberToBytes = function numberToBytes(number, _temp2) {
|
||||||
|
var _ref2 = _temp2 === void 0 ? {} : _temp2,
|
||||||
|
_ref2$le = _ref2.le,
|
||||||
|
le = _ref2$le === void 0 ? false : _ref2$le; // eslint-disable-next-line
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
|
||||||
|
number = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
number = BigInt(number);
|
||||||
|
var byteCount = countBytes(number);
|
||||||
|
var bytes = new Uint8Array(new ArrayBuffer(byteCount));
|
||||||
|
|
||||||
|
for (var i = 0; i < byteCount; i++) {
|
||||||
|
var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
|
||||||
|
bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
|
||||||
|
|
||||||
|
if (number < 0) {
|
||||||
|
bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
|
||||||
|
bytes[byteIndex] -= i === 0 ? 1 : 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
var stringToBytes = function stringToBytes(string, stringIsBytes) {
|
||||||
|
if (typeof string !== 'string' && string && typeof string.toString === 'function') {
|
||||||
|
string = string.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof string !== 'string') {
|
||||||
|
return new Uint8Array();
|
||||||
|
} // If the string already is bytes, we don't have to do this
|
||||||
|
// otherwise we do this so that we split multi length characters
|
||||||
|
// into individual bytes
|
||||||
|
|
||||||
|
|
||||||
|
if (!stringIsBytes) {
|
||||||
|
string = unescape(encodeURIComponent(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
var view = new Uint8Array(string.length);
|
||||||
|
|
||||||
|
for (var i = 0; i < string.length; i++) {
|
||||||
|
view[i] = string.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return view;
|
||||||
|
};
|
||||||
|
var concatTypedArrays = function concatTypedArrays() {
|
||||||
|
for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
|
||||||
|
buffers[_key] = arguments[_key];
|
||||||
|
}
|
||||||
|
|
||||||
|
buffers = buffers.filter(function (b) {
|
||||||
|
return b && (b.byteLength || b.length) && typeof b !== 'string';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (buffers.length <= 1) {
|
||||||
|
// for 0 length we will return empty uint8
|
||||||
|
// for 1 length we return the first uint8
|
||||||
|
return toUint8(buffers[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalLen = buffers.reduce(function (total, buf, i) {
|
||||||
|
return total + (buf.byteLength || buf.length);
|
||||||
|
}, 0);
|
||||||
|
var tempBuffer = new Uint8Array(totalLen);
|
||||||
|
var offset = 0;
|
||||||
|
buffers.forEach(function (buf) {
|
||||||
|
buf = toUint8(buf);
|
||||||
|
tempBuffer.set(buf, offset);
|
||||||
|
offset += buf.byteLength;
|
||||||
|
});
|
||||||
|
return tempBuffer;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Check if the bytes "b" are contained within bytes "a".
|
||||||
|
*
|
||||||
|
* @param {Uint8Array|Array} a
|
||||||
|
* Bytes to check in
|
||||||
|
*
|
||||||
|
* @param {Uint8Array|Array} b
|
||||||
|
* Bytes to check for
|
||||||
|
*
|
||||||
|
* @param {Object} options
|
||||||
|
* options
|
||||||
|
*
|
||||||
|
* @param {Array|Uint8Array} [offset=0]
|
||||||
|
* offset to use when looking at bytes in a
|
||||||
|
*
|
||||||
|
* @param {Array|Uint8Array} [mask=[]]
|
||||||
|
* mask to use on bytes before comparison.
|
||||||
|
*
|
||||||
|
* @return {boolean}
|
||||||
|
* If all bytes in b are inside of a, taking into account
|
||||||
|
* bit masks.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bytesMatch = function bytesMatch(a, b, _temp3) {
|
||||||
|
var _ref3 = _temp3 === void 0 ? {} : _temp3,
|
||||||
|
_ref3$offset = _ref3.offset,
|
||||||
|
offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
|
||||||
|
_ref3$mask = _ref3.mask,
|
||||||
|
mask = _ref3$mask === void 0 ? [] : _ref3$mask;
|
||||||
|
|
||||||
|
a = toUint8(a);
|
||||||
|
b = toUint8(b); // ie 11 does not support uint8 every
|
||||||
|
|
||||||
|
var fn = b.every ? b.every : Array.prototype.every;
|
||||||
|
return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
|
||||||
|
fn.call(b, function (bByte, i) {
|
||||||
|
var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
|
||||||
|
return bByte === aByte;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops through all supported media groups in master and calls the provided
|
* Loops through all supported media groups in master and calls the provided
|
||||||
* callback for each group
|
* callback for each group
|
||||||
@ -36516,7 +36714,7 @@
|
|||||||
|
|
||||||
var DOMParser = domParser.DOMParser;
|
var DOMParser = domParser.DOMParser;
|
||||||
|
|
||||||
/*! @name mpd-parser @version 0.21.0 @license Apache-2.0 */
|
/*! @name mpd-parser @version 0.21.1 @license Apache-2.0 */
|
||||||
|
|
||||||
var isObject = function isObject(obj) {
|
var isObject = function isObject(obj) {
|
||||||
return !!obj && typeof obj === 'object';
|
return !!obj && typeof obj === 'object';
|
||||||
@ -38733,7 +38931,15 @@
|
|||||||
|
|
||||||
var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
|
var generateKeySystemInformation = function generateKeySystemInformation(contentProtectionNodes) {
|
||||||
return contentProtectionNodes.reduce(function (acc, node) {
|
return contentProtectionNodes.reduce(function (acc, node) {
|
||||||
var attributes = parseAttributes(node);
|
var attributes = parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated
|
||||||
|
// as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
|
||||||
|
// UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
|
||||||
|
// .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
|
||||||
|
|
||||||
|
if (attributes.schemeIdUri) {
|
||||||
|
attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
var keySystem = keySystemsMap[attributes.schemeIdUri];
|
var keySystem = keySystemsMap[attributes.schemeIdUri];
|
||||||
|
|
||||||
if (keySystem) {
|
if (keySystem) {
|
||||||
@ -38744,8 +38950,7 @@
|
|||||||
|
|
||||||
if (psshNode) {
|
if (psshNode) {
|
||||||
var pssh = getContent(psshNode);
|
var pssh = getContent(psshNode);
|
||||||
var psshBuffer = pssh && decodeB64ToUint8Array(pssh);
|
acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh);
|
||||||
acc[keySystem].pssh = psshBuffer;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39268,186 +39473,6 @@
|
|||||||
|
|
||||||
var parseSidx_1 = parseSidx;
|
var parseSidx_1 = parseSidx;
|
||||||
|
|
||||||
// const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
|
|
||||||
// we used to do this with log2 but BigInt does not support builtin math
|
|
||||||
// Math.ceil(log2(x));
|
|
||||||
|
|
||||||
|
|
||||||
var countBits = function countBits(x) {
|
|
||||||
return x.toString(2).length;
|
|
||||||
}; // count the number of whole bytes it would take to represent a number
|
|
||||||
|
|
||||||
var countBytes = function countBytes(x) {
|
|
||||||
return Math.ceil(countBits(x) / 8);
|
|
||||||
};
|
|
||||||
var isTypedArray = function isTypedArray(obj) {
|
|
||||||
return ArrayBuffer.isView(obj);
|
|
||||||
};
|
|
||||||
var toUint8 = function toUint8(bytes) {
|
|
||||||
if (bytes instanceof Uint8Array) {
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
|
|
||||||
// any non-number or NaN leads to empty uint8array
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
|
|
||||||
bytes = 0;
|
|
||||||
} else {
|
|
||||||
bytes = [bytes];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
|
|
||||||
};
|
|
||||||
var BigInt = window.BigInt || Number;
|
|
||||||
var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
|
|
||||||
var bytesToNumber = function bytesToNumber(bytes, _temp) {
|
|
||||||
var _ref = _temp === void 0 ? {} : _temp,
|
|
||||||
_ref$signed = _ref.signed,
|
|
||||||
signed = _ref$signed === void 0 ? false : _ref$signed,
|
|
||||||
_ref$le = _ref.le,
|
|
||||||
le = _ref$le === void 0 ? false : _ref$le;
|
|
||||||
|
|
||||||
bytes = toUint8(bytes);
|
|
||||||
var fn = le ? 'reduce' : 'reduceRight';
|
|
||||||
var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
|
|
||||||
var number = obj.call(bytes, function (total, _byte, i) {
|
|
||||||
var exponent = le ? i : Math.abs(i + 1 - bytes.length);
|
|
||||||
return total + BigInt(_byte) * BYTE_TABLE[exponent];
|
|
||||||
}, BigInt(0));
|
|
||||||
|
|
||||||
if (signed) {
|
|
||||||
var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
|
|
||||||
number = BigInt(number);
|
|
||||||
|
|
||||||
if (number > max) {
|
|
||||||
number -= max;
|
|
||||||
number -= max;
|
|
||||||
number -= BigInt(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Number(number);
|
|
||||||
};
|
|
||||||
var numberToBytes = function numberToBytes(number, _temp2) {
|
|
||||||
var _ref2 = _temp2 === void 0 ? {} : _temp2,
|
|
||||||
_ref2$le = _ref2.le,
|
|
||||||
le = _ref2$le === void 0 ? false : _ref2$le; // eslint-disable-next-line
|
|
||||||
|
|
||||||
|
|
||||||
if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
|
|
||||||
number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
number = BigInt(number);
|
|
||||||
var byteCount = countBytes(number);
|
|
||||||
var bytes = new Uint8Array(new ArrayBuffer(byteCount));
|
|
||||||
|
|
||||||
for (var i = 0; i < byteCount; i++) {
|
|
||||||
var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
|
|
||||||
bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
|
|
||||||
|
|
||||||
if (number < 0) {
|
|
||||||
bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
|
|
||||||
bytes[byteIndex] -= i === 0 ? 1 : 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes;
|
|
||||||
};
|
|
||||||
var stringToBytes = function stringToBytes(string, stringIsBytes) {
|
|
||||||
if (typeof string !== 'string' && string && typeof string.toString === 'function') {
|
|
||||||
string = string.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof string !== 'string') {
|
|
||||||
return new Uint8Array();
|
|
||||||
} // If the string already is bytes, we don't have to do this
|
|
||||||
// otherwise we do this so that we split multi length characters
|
|
||||||
// into individual bytes
|
|
||||||
|
|
||||||
|
|
||||||
if (!stringIsBytes) {
|
|
||||||
string = unescape(encodeURIComponent(string));
|
|
||||||
}
|
|
||||||
|
|
||||||
var view = new Uint8Array(string.length);
|
|
||||||
|
|
||||||
for (var i = 0; i < string.length; i++) {
|
|
||||||
view[i] = string.charCodeAt(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return view;
|
|
||||||
};
|
|
||||||
var concatTypedArrays = function concatTypedArrays() {
|
|
||||||
for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
||||||
buffers[_key] = arguments[_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
buffers = buffers.filter(function (b) {
|
|
||||||
return b && (b.byteLength || b.length) && typeof b !== 'string';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buffers.length <= 1) {
|
|
||||||
// for 0 length we will return empty uint8
|
|
||||||
// for 1 length we return the first uint8
|
|
||||||
return toUint8(buffers[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var totalLen = buffers.reduce(function (total, buf, i) {
|
|
||||||
return total + (buf.byteLength || buf.length);
|
|
||||||
}, 0);
|
|
||||||
var tempBuffer = new Uint8Array(totalLen);
|
|
||||||
var offset = 0;
|
|
||||||
buffers.forEach(function (buf) {
|
|
||||||
buf = toUint8(buf);
|
|
||||||
tempBuffer.set(buf, offset);
|
|
||||||
offset += buf.byteLength;
|
|
||||||
});
|
|
||||||
return tempBuffer;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Check if the bytes "b" are contained within bytes "a".
|
|
||||||
*
|
|
||||||
* @param {Uint8Array|Array} a
|
|
||||||
* Bytes to check in
|
|
||||||
*
|
|
||||||
* @param {Uint8Array|Array} b
|
|
||||||
* Bytes to check for
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* options
|
|
||||||
*
|
|
||||||
* @param {Array|Uint8Array} [offset=0]
|
|
||||||
* offset to use when looking at bytes in a
|
|
||||||
*
|
|
||||||
* @param {Array|Uint8Array} [mask=[]]
|
|
||||||
* mask to use on bytes before comparison.
|
|
||||||
*
|
|
||||||
* @return {boolean}
|
|
||||||
* If all bytes in b are inside of a, taking into account
|
|
||||||
* bit masks.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bytesMatch = function bytesMatch(a, b, _temp3) {
|
|
||||||
var _ref3 = _temp3 === void 0 ? {} : _temp3,
|
|
||||||
_ref3$offset = _ref3.offset,
|
|
||||||
offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
|
|
||||||
_ref3$mask = _ref3.mask,
|
|
||||||
mask = _ref3$mask === void 0 ? [] : _ref3$mask;
|
|
||||||
|
|
||||||
a = toUint8(a);
|
|
||||||
b = toUint8(b); // ie 11 does not support uint8 every
|
|
||||||
|
|
||||||
var fn = b.every ? b.every : Array.prototype.every;
|
|
||||||
return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
|
|
||||||
fn.call(b, function (bByte, i) {
|
|
||||||
var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
|
|
||||||
return bByte === aByte;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var ID3 = toUint8([0x49, 0x44, 0x33]);
|
var ID3 = toUint8([0x49, 0x44, 0x33]);
|
||||||
var getId3Size = function getId3Size(bytes, offset) {
|
var getId3Size = function getId3Size(bytes, offset) {
|
||||||
if (offset === void 0) {
|
if (offset === void 0) {
|
||||||
@ -40130,7 +40155,7 @@
|
|||||||
};
|
};
|
||||||
var clock_1 = clock.ONE_SECOND_IN_TS;
|
var clock_1 = clock.ONE_SECOND_IN_TS;
|
||||||
|
|
||||||
/*! @name @videojs/http-streaming @version 2.14.0 @license Apache-2.0 */
|
/*! @name @videojs/http-streaming @version 2.14.2 @license Apache-2.0 */
|
||||||
/**
|
/**
|
||||||
* @file resolve-url.js - Handling how URLs are resolved and manipulated
|
* @file resolve-url.js - Handling how URLs are resolved and manipulated
|
||||||
*/
|
*/
|
||||||
@ -41964,7 +41989,7 @@
|
|||||||
|
|
||||||
for (var _i2 = 0; _i2 < properties.playlists.length; _i2++) {
|
for (var _i2 = 0; _i2 < properties.playlists.length; _i2++) {
|
||||||
if (newMedia.id === properties.playlists[_i2].id) {
|
if (newMedia.id === properties.playlists[_i2].id) {
|
||||||
properties.playlists[_i2] = newMedia;
|
properties.playlists[_i2] = mergedPlaylist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -42747,7 +42772,7 @@
|
|||||||
Object.keys(message).forEach(function (key) {
|
Object.keys(message).forEach(function (key) {
|
||||||
var value = message[key];
|
var value = message[key];
|
||||||
|
|
||||||
if (ArrayBuffer.isView(value)) {
|
if (isArrayBufferView(value)) {
|
||||||
transferable[key] = {
|
transferable[key] = {
|
||||||
bytes: value.buffer,
|
bytes: value.buffer,
|
||||||
byteOffset: value.byteOffset,
|
byteOffset: value.byteOffset,
|
||||||
@ -44241,7 +44266,7 @@
|
|||||||
var getWorkerString = function getWorkerString(fn) {
|
var getWorkerString = function getWorkerString(fn) {
|
||||||
return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
|
return fn.toString().replace(/^function.+?{/, '').slice(0, -1);
|
||||||
};
|
};
|
||||||
/* rollup-plugin-worker-factory start for worker!/Users/poneill/dev/http-streaming/src/transmuxer-worker.js */
|
/* rollup-plugin-worker-factory start for worker!/Users/bclifford/Code/vhs-release-test/src/transmuxer-worker.js */
|
||||||
|
|
||||||
|
|
||||||
var workerCode$1 = transform(getWorkerString(function () {
|
var workerCode$1 = transform(getWorkerString(function () {
|
||||||
@ -53047,7 +53072,7 @@
|
|||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
var TransmuxWorker = factory(workerCode$1);
|
var TransmuxWorker = factory(workerCode$1);
|
||||||
/* rollup-plugin-worker-factory end for worker!/Users/poneill/dev/http-streaming/src/transmuxer-worker.js */
|
/* rollup-plugin-worker-factory end for worker!/Users/bclifford/Code/vhs-release-test/src/transmuxer-worker.js */
|
||||||
|
|
||||||
var handleData_ = function handleData_(event, transmuxedData, callback) {
|
var handleData_ = function handleData_(event, transmuxedData, callback) {
|
||||||
var _event$data$segment = event.data.segment,
|
var _event$data$segment = event.data.segment,
|
||||||
@ -60834,10 +60859,12 @@
|
|||||||
|
|
||||||
return TimelineChangeController;
|
return TimelineChangeController;
|
||||||
}(videojs.EventTarget);
|
}(videojs.EventTarget);
|
||||||
/* rollup-plugin-worker-factory start for worker!/Users/poneill/dev/http-streaming/src/decrypter-worker.js */
|
/* rollup-plugin-worker-factory start for worker!/Users/bclifford/Code/vhs-release-test/src/decrypter-worker.js */
|
||||||
|
|
||||||
|
|
||||||
var workerCode = transform(getWorkerString(function () {
|
var workerCode = transform(getWorkerString(function () {
|
||||||
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
||||||
|
|
||||||
function createCommonjsModule(fn, basedir, module) {
|
function createCommonjsModule(fn, basedir, module) {
|
||||||
return module = {
|
return module = {
|
||||||
path: basedir,
|
path: basedir,
|
||||||
@ -61030,7 +61057,7 @@
|
|||||||
function unpad(padded) {
|
function unpad(padded) {
|
||||||
return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
|
return padded.subarray(0, padded.byteLength - padded[padded.byteLength - 1]);
|
||||||
}
|
}
|
||||||
/*! @name aes-decrypter @version 3.1.2 @license Apache-2.0 */
|
/*! @name aes-decrypter @version 3.1.3 @license Apache-2.0 */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @file aes.js
|
* @file aes.js
|
||||||
@ -61455,10 +61482,31 @@
|
|||||||
}]);
|
}]);
|
||||||
return Decrypter;
|
return Decrypter;
|
||||||
}();
|
}();
|
||||||
/**
|
|
||||||
* @file bin-utils.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
var win;
|
||||||
|
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
win = window;
|
||||||
|
} else if (typeof commonjsGlobal !== "undefined") {
|
||||||
|
win = commonjsGlobal;
|
||||||
|
} else if (typeof self !== "undefined") {
|
||||||
|
win = self;
|
||||||
|
} else {
|
||||||
|
win = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
var window_1 = win;
|
||||||
|
|
||||||
|
var isArrayBufferView = function isArrayBufferView(obj) {
|
||||||
|
if (ArrayBuffer.isView === 'function') {
|
||||||
|
return ArrayBuffer.isView(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj && obj.buffer instanceof ArrayBuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
var BigInt = window_1.BigInt || Number;
|
||||||
|
[BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
|
||||||
/**
|
/**
|
||||||
* Creates an object for sending to a web worker modifying properties that are TypedArrays
|
* Creates an object for sending to a web worker modifying properties that are TypedArrays
|
||||||
* into a new object with seperated properties for the buffer, byteOffset, and byteLength.
|
* into a new object with seperated properties for the buffer, byteOffset, and byteLength.
|
||||||
@ -61476,7 +61524,7 @@
|
|||||||
Object.keys(message).forEach(function (key) {
|
Object.keys(message).forEach(function (key) {
|
||||||
var value = message[key];
|
var value = message[key];
|
||||||
|
|
||||||
if (ArrayBuffer.isView(value)) {
|
if (isArrayBufferView(value)) {
|
||||||
transferable[key] = {
|
transferable[key] = {
|
||||||
bytes: value.buffer,
|
bytes: value.buffer,
|
||||||
byteOffset: value.byteOffset,
|
byteOffset: value.byteOffset,
|
||||||
@ -61514,7 +61562,7 @@
|
|||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
var Decrypter = factory(workerCode);
|
var Decrypter = factory(workerCode);
|
||||||
/* rollup-plugin-worker-factory end for worker!/Users/poneill/dev/http-streaming/src/decrypter-worker.js */
|
/* rollup-plugin-worker-factory end for worker!/Users/bclifford/Code/vhs-release-test/src/decrypter-worker.js */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the properties of an HLS track into an audioTrackKind.
|
* Convert the properties of an HLS track into an audioTrackKind.
|
||||||
@ -65350,11 +65398,11 @@
|
|||||||
initPlugin(this, options);
|
initPlugin(this, options);
|
||||||
};
|
};
|
||||||
|
|
||||||
var version$4 = "2.14.0";
|
var version$4 = "2.14.2";
|
||||||
var version$3 = "6.0.1";
|
var version$3 = "6.0.1";
|
||||||
var version$2 = "0.21.0";
|
var version$2 = "0.21.1";
|
||||||
var version$1 = "4.7.0";
|
var version$1 = "4.7.1";
|
||||||
var version = "3.1.2";
|
var version = "3.1.3";
|
||||||
var Vhs = {
|
var Vhs = {
|
||||||
PlaylistLoader: PlaylistLoader,
|
PlaylistLoader: PlaylistLoader,
|
||||||
Playlist: Playlist,
|
Playlist: Playlist,
|
||||||
|
|||||||
17
public/_player/videojs/dist/video.min.js
vendored
17
public/_player/videojs/dist/video.min.js
vendored
File diff suppressed because one or more lines are too long
80
public/_player/videojs/dist/videojs-license.js
vendored
80
public/_player/videojs/dist/videojs-license.js
vendored
@ -1,13 +1,14 @@
|
|||||||
/*! @name videojs-license @version 0.1.0 @license MIT */
|
/*! @name videojs-license @version 0.1.0 @license MIT */
|
||||||
(function (global, factory) {
|
(function (global, factory) {
|
||||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js'), require('global/document')) :
|
||||||
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
|
typeof define === 'function' && define.amd ? define(['video.js', 'global/document'], factory) :
|
||||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs));
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs, global.document));
|
||||||
})(this, (function (videojs) { 'use strict';
|
}(this, (function (videojs, document) { 'use strict';
|
||||||
|
|
||||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||||
|
|
||||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||||
|
var document__default = /*#__PURE__*/_interopDefaultLegacy(document);
|
||||||
|
|
||||||
function createCommonjsModule(fn, basedir, module) {
|
function createCommonjsModule(fn, basedir, module) {
|
||||||
return module = {
|
return module = {
|
||||||
@ -32,7 +33,8 @@
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = _assertThisInitialized, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
module.exports = _assertThisInitialized;
|
||||||
|
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
var setPrototypeOf = createCommonjsModule(function (module) {
|
var setPrototypeOf = createCommonjsModule(function (module) {
|
||||||
@ -40,11 +42,14 @@
|
|||||||
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||||
o.__proto__ = p;
|
o.__proto__ = p;
|
||||||
return o;
|
return o;
|
||||||
}, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
};
|
||||||
|
|
||||||
|
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||||
return _setPrototypeOf(o, p);
|
return _setPrototypeOf(o, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = _setPrototypeOf, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
module.exports = _setPrototypeOf;
|
||||||
|
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
var inheritsLoose = createCommonjsModule(function (module) {
|
var inheritsLoose = createCommonjsModule(function (module) {
|
||||||
@ -54,14 +59,15 @@
|
|||||||
setPrototypeOf(subClass, superClass);
|
setPrototypeOf(subClass, superClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = _inheritsLoose, module.exports.__esModule = true, module.exports["default"] = module.exports;
|
module.exports = _inheritsLoose;
|
||||||
|
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
var version = "0.1.0";
|
var version = "0.1.0";
|
||||||
|
|
||||||
var Plugin = videojs__default["default"].getPlugin('plugin');
|
var Plugin = videojs__default['default'].getPlugin('plugin');
|
||||||
var Component = videojs__default["default"].getComponent('Component');
|
var Component = videojs__default['default'].getComponent('Component');
|
||||||
var Button = videojs__default["default"].getComponent('MenuButton'); // Default options for the plugin.
|
var Button = videojs__default['default'].getComponent('MenuButton'); // Default options for the plugin.
|
||||||
|
|
||||||
var defaults = {
|
var defaults = {
|
||||||
license: 'none',
|
license: 'none',
|
||||||
@ -100,7 +106,7 @@
|
|||||||
// the parent class will add player under this.player
|
// the parent class will add player under this.player
|
||||||
_this = _Plugin.call(this, player) || this;
|
_this = _Plugin.call(this, player) || this;
|
||||||
_this.playerId = _this.player.id();
|
_this.playerId = _this.player.id();
|
||||||
_this.options = videojs__default["default"].mergeOptions(defaults, options);
|
_this.options = videojs__default['default'].mergeOptions(defaults, options);
|
||||||
|
|
||||||
if (options.license === 'none') {
|
if (options.license === 'none') {
|
||||||
return assertThisInitialized(_this);
|
return assertThisInitialized(_this);
|
||||||
@ -111,14 +117,14 @@
|
|||||||
|
|
||||||
_this.buildUI();
|
_this.buildUI();
|
||||||
|
|
||||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||||
_this.mobileBuildUI();
|
_this.mobileBuildUI();
|
||||||
}
|
}
|
||||||
}); // close the menu if open on userinactive
|
}); // close the menu if open on userinactive
|
||||||
|
|
||||||
|
|
||||||
_this.player.on('userinactive', function () {
|
_this.player.on('userinactive', function () {
|
||||||
document.getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
||||||
element.classList.remove('vjs-lock-open');
|
element.classList.remove('vjs-lock-open');
|
||||||
});
|
});
|
||||||
}); // close the menu if anywhere in the player is clicked
|
}); // close the menu if anywhere in the player is clicked
|
||||||
@ -126,7 +132,7 @@
|
|||||||
|
|
||||||
_this.player.on('click', function (evt) {
|
_this.player.on('click', function (evt) {
|
||||||
if (evt.target.tagName === 'VIDEO') {
|
if (evt.target.tagName === 'VIDEO') {
|
||||||
document.getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
||||||
element.classList.remove('vjs-lock-open');
|
element.classList.remove('vjs-lock-open');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -135,7 +141,7 @@
|
|||||||
_this.player.on('loadstart', function (_event) {
|
_this.player.on('loadstart', function (_event) {
|
||||||
_this.removeElementsByClass('vjs-license-clear');
|
_this.removeElementsByClass('vjs-license-clear');
|
||||||
|
|
||||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||||
_this.mobileBuildTopLevelMenu();
|
_this.mobileBuildTopLevelMenu();
|
||||||
} else {
|
} else {
|
||||||
_this.buildTopLevelMenu();
|
_this.buildTopLevelMenu();
|
||||||
@ -190,7 +196,7 @@
|
|||||||
var _proto2 = LicenseMenuButton.prototype;
|
var _proto2 = LicenseMenuButton.prototype;
|
||||||
|
|
||||||
_proto2.handleClick = function handleClick() {
|
_proto2.handleClick = function handleClick() {
|
||||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||||
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
|
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
this.el().classList.toggle('vjs-toogle-btn');
|
this.el().classList.toggle('vjs-toogle-btn');
|
||||||
@ -201,7 +207,7 @@
|
|||||||
return LicenseMenuButton;
|
return LicenseMenuButton;
|
||||||
}(Button);
|
}(Button);
|
||||||
|
|
||||||
videojs__default["default"].registerComponent('licenseMenuButton', LicenseMenuButton);
|
videojs__default['default'].registerComponent('licenseMenuButton', LicenseMenuButton);
|
||||||
this.player.getChild('controlBar').addChild('licenseMenuButton');
|
this.player.getChild('controlBar').addChild('licenseMenuButton');
|
||||||
|
|
||||||
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
|
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
|
||||||
@ -222,26 +228,26 @@
|
|||||||
main.innerHTML = '';
|
main.innerHTML = '';
|
||||||
main.classList.add('vjs-license-top-level'); // Start building new list items
|
main.classList.add('vjs-license-top-level'); // Start building new list items
|
||||||
|
|
||||||
var menuTitle = document.createElement('li');
|
var menuTitle = document__default['default'].createElement('li');
|
||||||
menuTitle.className = 'vjs-license-top-level-header';
|
menuTitle.className = 'vjs-license-top-level-header';
|
||||||
var menuTitleInner = document.createElement('span');
|
var menuTitleInner = document__default['default'].createElement('span');
|
||||||
menuTitleInner.innerHTML = 'About';
|
menuTitleInner.innerHTML = 'About';
|
||||||
menuTitleInner.className = 'vjs-license-top-level-header-titel';
|
menuTitleInner.className = 'vjs-license-top-level-header-titel';
|
||||||
menuTitle.appendChild(menuTitleInner);
|
menuTitle.appendChild(menuTitleInner);
|
||||||
main.appendChild(menuTitle);
|
main.appendChild(menuTitle);
|
||||||
var itemTitel = document.createElement('li');
|
var itemTitel = document__default['default'].createElement('li');
|
||||||
itemTitel.innerHTML = this.buildItemTitel();
|
itemTitel.innerHTML = this.buildItemTitel();
|
||||||
itemTitel.className = 'vjs-license-top-level-item';
|
itemTitel.className = 'vjs-license-top-level-item';
|
||||||
main.appendChild(itemTitel);
|
main.appendChild(itemTitel);
|
||||||
|
|
||||||
if (this.options.author) {
|
if (this.options.author) {
|
||||||
var itemAuthor = document.createElement('li');
|
var itemAuthor = document__default['default'].createElement('li');
|
||||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||||
itemAuthor.className = 'vjs-license-top-level-item';
|
itemAuthor.className = 'vjs-license-top-level-item';
|
||||||
main.appendChild(itemAuthor);
|
main.appendChild(itemAuthor);
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemLicense = document.createElement('li');
|
var itemLicense = document__default['default'].createElement('li');
|
||||||
itemLicense.innerHTML = this.buildItemLicense();
|
itemLicense.innerHTML = this.buildItemLicense();
|
||||||
itemLicense.className = 'vjs-license-top-level-item';
|
itemLicense.className = 'vjs-license-top-level-item';
|
||||||
main.appendChild(itemLicense);
|
main.appendChild(itemLicense);
|
||||||
@ -277,7 +283,7 @@
|
|||||||
var _proto3 = LicenseMenuMobileModal.prototype;
|
var _proto3 = LicenseMenuMobileModal.prototype;
|
||||||
|
|
||||||
_proto3.createEl = function createEl() {
|
_proto3.createEl = function createEl() {
|
||||||
return videojs__default["default"].createEl('div', {
|
return videojs__default['default'].createEl('div', {
|
||||||
className: 'vjs-license-mobile'
|
className: 'vjs-license-mobile'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -285,8 +291,8 @@
|
|||||||
return LicenseMenuMobileModal;
|
return LicenseMenuMobileModal;
|
||||||
}(Component);
|
}(Component);
|
||||||
|
|
||||||
videojs__default["default"].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
|
videojs__default['default'].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
|
||||||
videojs__default["default"].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document.body);
|
videojs__default['default'].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document__default['default'].body);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Add the menu ui button to the controlbar
|
* Add the menu ui button to the controlbar
|
||||||
@ -297,28 +303,28 @@
|
|||||||
var _this3 = this;
|
var _this3 = this;
|
||||||
|
|
||||||
var settingsButton = this.player.getChild('licenseMenuMobileModal');
|
var settingsButton = this.player.getChild('licenseMenuMobileModal');
|
||||||
var menuTopLevel = document.createElement('ul');
|
var menuTopLevel = document__default['default'].createElement('ul');
|
||||||
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
|
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
|
||||||
settingsButton.el().appendChild(menuTopLevel); // Empty the main menu div to repopulate
|
settingsButton.el().appendChild(menuTopLevel); // Empty the main menu div to repopulate
|
||||||
|
|
||||||
var menuTitle = document.createElement('li');
|
var menuTitle = document__default['default'].createElement('li');
|
||||||
menuTitle.innerHTML = 'About';
|
menuTitle.innerHTML = 'About';
|
||||||
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
|
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
|
||||||
menuTopLevel.appendChild(menuTitle);
|
menuTopLevel.appendChild(menuTitle);
|
||||||
var itemTitel = document.createElement('li');
|
var itemTitel = document__default['default'].createElement('li');
|
||||||
itemTitel.innerHTML = this.buildItemTitel();
|
itemTitel.innerHTML = this.buildItemTitel();
|
||||||
itemTitel.className = 'vjs-license-top-level-item';
|
itemTitel.className = 'vjs-license-top-level-item';
|
||||||
|
|
||||||
if (this.options.author) {
|
if (this.options.author) {
|
||||||
var itemAuthor = document.createElement('li');
|
var itemAuthor = document__default['default'].createElement('li');
|
||||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||||
itemAuthor.className = 'vjs-license-top-level-item';
|
itemAuthor.className = 'vjs-license-top-level-item';
|
||||||
}
|
}
|
||||||
|
|
||||||
var itemLicense = document.createElement('li');
|
var itemLicense = document__default['default'].createElement('li');
|
||||||
itemLicense.innerHTML = this.buildItemLicense();
|
itemLicense.innerHTML = this.buildItemLicense();
|
||||||
itemLicense.className = 'vjs-license-top-level-item';
|
itemLicense.className = 'vjs-license-top-level-item';
|
||||||
var menuClose = document.createElement('li');
|
var menuClose = document__default['default'].createElement('li');
|
||||||
menuClose.innerHTML = 'Close';
|
menuClose.innerHTML = 'Close';
|
||||||
menuClose.className = 'setting-menu-footer-default';
|
menuClose.className = 'setting-menu-footer-default';
|
||||||
|
|
||||||
@ -342,7 +348,7 @@
|
|||||||
titel = "" + this.options.title;
|
titel = "" + this.options.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 'Titel: ' + titel;
|
return 'Title: ' + titel;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Add the menu ui button to the controlbar
|
* Add the menu ui button to the controlbar
|
||||||
@ -428,10 +434,10 @@
|
|||||||
|
|
||||||
_proto.removeElementsByClass = function removeElementsByClass(className) {
|
_proto.removeElementsByClass = function removeElementsByClass(className) {
|
||||||
// Need to prevent the menu from not showing sometimes
|
// Need to prevent the menu from not showing sometimes
|
||||||
document.querySelectorAll('.vjs-sm-top-level').forEach(function (element) {
|
document__default['default'].querySelectorAll('.vjs-sm-top-level').forEach(function (element) {
|
||||||
element.classList.remove('vjs-hidden');
|
element.classList.remove('vjs-hidden');
|
||||||
});
|
});
|
||||||
var elements = document.getElementsByClassName(className);
|
var elements = document__default['default'].getElementsByClassName(className);
|
||||||
|
|
||||||
while (elements.length > 0) {
|
while (elements.length > 0) {
|
||||||
elements[0].parentNode.removeChild(elements[0]);
|
elements[0].parentNode.removeChild(elements[0]);
|
||||||
@ -446,8 +452,8 @@
|
|||||||
|
|
||||||
License.VERSION = version; // Register the plugin with video.js.
|
License.VERSION = version; // Register the plugin with video.js.
|
||||||
|
|
||||||
videojs__default["default"].registerPlugin('license', License);
|
videojs__default['default'].registerPlugin('license', License);
|
||||||
|
|
||||||
return License;
|
return License;
|
||||||
|
|
||||||
}));
|
})));
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -14,7 +14,7 @@
|
|||||||
{{#if channel_creator_name}}
|
{{#if channel_creator_name}}
|
||||||
<meta name="author" content="{{channel_creator_name}}">
|
<meta name="author" content="{{channel_creator_name}}">
|
||||||
{{/if}}
|
{{/if}}
|
||||||
<meta property="og:site_name" content="{{titel}}">
|
<meta property="og:site_name" content="{{title}}">
|
||||||
<meta property="og:url" content="{{url}}">
|
<meta property="og:url" content="{{url}}">
|
||||||
<meta property="og:title" content="{{channel_name}}">
|
<meta property="og:title" content="{{channel_name}}">
|
||||||
<meta property="og:image" content="{{channel_poster}}">
|
<meta property="og:image" content="{{channel_poster}}">
|
||||||
@ -99,7 +99,7 @@
|
|||||||
background-color: {{bgcolor_header}};
|
background-color: {{bgcolor_header}};
|
||||||
}
|
}
|
||||||
.header-title {
|
.header-title {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding-left: .5em;
|
padding-left: .5em;
|
||||||
@ -181,7 +181,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.content-headline {
|
.content-headline {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
@ -195,13 +195,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.channel-stats {
|
.channel-stats {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding-top: 0.6em;
|
padding-top: 0.6em;
|
||||||
padding-bottom: 0.8em;
|
padding-bottom: 0.8em;
|
||||||
}
|
}
|
||||||
.channel-stats-icon {
|
.channel-stats-icon {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
font-size: 1.3rem !important;
|
font-size: 1.3rem !important;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
@ -344,14 +344,14 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
.close {
|
.close {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
float: right;
|
float: right;
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
.close:hover,
|
.close:hover,
|
||||||
.close:focus {
|
.close:focus {
|
||||||
color: {{textcolor_titel}};
|
color: {{textcolor_title}};
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
@ -364,7 +364,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 middle-xs header">
|
<div class="col-xs-12 middle-xs header">
|
||||||
<h1 class="header-title">{{titel}}</h1>
|
<h1 class="header-title">{{title}}</h1>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row max-width">
|
<div class="row max-width">
|
||||||
@ -397,7 +397,9 @@
|
|||||||
<span id="channel_stats_runtime_{{channel_id}}"></span> <span class="material-icons inline-icon channel-stats-icon">live_tv</span>
|
<span id="channel_stats_runtime_{{channel_id}}"></span> <span class="material-icons inline-icon channel-stats-icon">live_tv</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-3 middle-xs right-align">
|
<div class="col-xs-3 middle-xs right-align">
|
||||||
|
{{#if share}}
|
||||||
<a id="btn-share" href="#"><span class="material-icons inline-icon">share</span> SHARE</a>
|
<a id="btn-share" href="#"><span class="material-icons inline-icon">share</span> SHARE</a>
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
@ -411,8 +413,10 @@
|
|||||||
<strong>Creator:</strong> <a id="btn-creator" href="#">{{channel_creator_name}}</a><br />
|
<strong>Creator:</strong> <a id="btn-creator" href="#">{{channel_creator_name}}</a><br />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if channel_license}}
|
{{#if channel_license}}
|
||||||
|
{{#ifnoteq channel_license "none"}}
|
||||||
<strong>Content license:</strong><br />
|
<strong>Content license:</strong><br />
|
||||||
<a id="license" target="_blank" rel="noopener"><img id="license_image" class="channel-license-image" height="40" width="114" /></a>
|
<a id="license" target="_blank" rel="noopener"><img id="license_image" class="channel-license-image" height="40" width="114" /></a>
|
||||||
|
{{/ifnoteq}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,12 +2,12 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta name="description" content="Restreamer – Video-Streaming" />
|
<meta name="description" content="Restreamer – Video-Streaming" />
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="logo192.png" />
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
<title>Restreamer</title>
|
<title>Restreamer</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@ -132,7 +132,7 @@ function Resources(props) {
|
|||||||
const core = $resources.core;
|
const core = $resources.core;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="footerRight" direction="row" alignItems="center" spacing={0} >
|
<Stack className="footerRight" direction="row" alignItems="center" spacing={0}>
|
||||||
{(system.cpu_used >= 75 || system.mem_used >= 75 || core.memfs_used >= 75 || core.disk_used >= 75 || core.net_used >= 75) && (
|
{(system.cpu_used >= 75 || system.mem_used >= 75 || core.memfs_used >= 75 || core.disk_used >= 75 || core.net_used >= 75) && (
|
||||||
<WarningIcon className={classes.warningIcon} color="service" />
|
<WarningIcon className={classes.warningIcon} color="service" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -182,10 +182,7 @@ function AboutModal(props) {
|
|||||||
<Grid item xs={12}></Grid>
|
<Grid item xs={12}></Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<strong>Release</strong>:{' '}
|
<strong>Release</strong>: {Version.UI}
|
||||||
<Link color="secondary" target="_blank" href={'https://github.com/datarhei/restreamer/releases/tag/v' + Version.UI}>
|
|
||||||
v{Version.UI}
|
|
||||||
</Link>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography>
|
<Typography>
|
||||||
<strong>Repo</strong>:{' '}
|
<strong>Repo</strong>:{' '}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { messages as FR } from './locales/fr/messages.js';
|
|||||||
import { messages as IT } from './locales/it/messages.js';
|
import { messages as IT } from './locales/it/messages.js';
|
||||||
import { messages as PT } from './locales/pt/messages.js';
|
import { messages as PT } from './locales/pt/messages.js';
|
||||||
import { messages as ES } from './locales/es/messages.js';
|
import { messages as ES } from './locales/es/messages.js';
|
||||||
|
import { messages as RU } from './locales/ru/messages.js';
|
||||||
import * as Storage from './utils/storage';
|
import * as Storage from './utils/storage';
|
||||||
|
|
||||||
i18n.loadLocaleData('en', { plurals: plurals.en });
|
i18n.loadLocaleData('en', { plurals: plurals.en });
|
||||||
@ -18,6 +19,7 @@ i18n.loadLocaleData('fr', { plurals: plurals.fr });
|
|||||||
i18n.loadLocaleData('it', { plurals: plurals.it });
|
i18n.loadLocaleData('it', { plurals: plurals.it });
|
||||||
i18n.loadLocaleData('pt', { plurals: plurals.pt });
|
i18n.loadLocaleData('pt', { plurals: plurals.pt });
|
||||||
i18n.loadLocaleData('es', { plurals: plurals.es });
|
i18n.loadLocaleData('es', { plurals: plurals.es });
|
||||||
|
i18n.loadLocaleData('ru', { plurals: plurals.ru });
|
||||||
i18n.load({
|
i18n.load({
|
||||||
en: EN,
|
en: EN,
|
||||||
de: DE,
|
de: DE,
|
||||||
@ -25,6 +27,7 @@ i18n.load({
|
|||||||
it: IT,
|
it: IT,
|
||||||
pt: PT,
|
pt: PT,
|
||||||
es: ES,
|
es: ES,
|
||||||
|
ru: RU,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||||
@ -53,7 +56,7 @@ const getBrowserLanguage = (defaultLanguage) => {
|
|||||||
return match[0].toLowerCase();
|
return match[0].toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n.activate(getLanguage('en', ['en', 'de', 'fr', 'it', 'pt', 'es']));
|
i18n.activate(getLanguage('en', ['en', 'de', 'fr', 'it', 'pt', 'es', 'ru']));
|
||||||
|
|
||||||
export default function Provider(props) {
|
export default function Provider(props) {
|
||||||
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/locales/ru/messages.js
Normal file
1
src/locales/ru/messages.js
Normal file
File diff suppressed because one or more lines are too long
2670
src/locales/ru/messages.po
Normal file
2670
src/locales/ru/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -50,7 +50,9 @@ export default function Component(props) {
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={1}
|
spacing={1}
|
||||||
className={props.color === 'dark' ? classes.dark : props.color === 'success' ? classes.success : props.color === 'danger' ? classes.danger : classes.light}
|
className={
|
||||||
|
props.color === 'dark' ? classes.dark : props.color === 'success' ? classes.success : props.color === 'danger' ? classes.danger : classes.light
|
||||||
|
}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
@ -59,5 +61,5 @@ export default function Component(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.defaultProps = {
|
Component.defaultProps = {
|
||||||
color: 'light'
|
color: 'light',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -55,7 +55,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
marginRight: '-1.5em!important',
|
marginRight: '-1.5em!important',
|
||||||
paddingBottom: '1em',
|
paddingBottom: '1em',
|
||||||
},
|
},
|
||||||
imageTitel: {
|
imageTitle: {
|
||||||
textAlign: 'initial',
|
textAlign: 'initial',
|
||||||
padding: '.5em 0em 0em .1em',
|
padding: '.5em 0em 0em .1em',
|
||||||
},
|
},
|
||||||
@ -169,7 +169,7 @@ function ChannelButton(props) {
|
|||||||
<ImageSrc style={{ backgroundImage: `url(${props.url})`, borderColor: color_active }} />
|
<ImageSrc style={{ backgroundImage: `url(${props.url})`, borderColor: color_active }} />
|
||||||
<ImageBackdrop className="MuiImageBackdrop-root" style={{ borderColor: color_active }} />
|
<ImageBackdrop className="MuiImageBackdrop-root" style={{ borderColor: color_active }} />
|
||||||
</Image>
|
</Image>
|
||||||
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" className={classes.imageTitel}>
|
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" className={classes.imageTitle}>
|
||||||
<Typography variant="body2" color="inherit">
|
<Typography variant="body2" color="inherit">
|
||||||
{props.title}
|
{props.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
35
src/misc/CopyButton.js
Normal file
35
src/misc/CopyButton.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React, { useContext } from 'react';
|
||||||
|
|
||||||
|
import { useLingui } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import FileCopyIcon from '@mui/icons-material/FileCopy';
|
||||||
|
|
||||||
|
import CopyToClipboard from '../utils/clipboard';
|
||||||
|
import NotifyContext from '../contexts/Notify';
|
||||||
|
|
||||||
|
export default function CopyButton(props) {
|
||||||
|
const notify = useContext(NotifyContext);
|
||||||
|
const { i18n } = useLingui();
|
||||||
|
const { children, value, ...other } = props;
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
const success = await CopyToClipboard(value);
|
||||||
|
|
||||||
|
if (success === true) {
|
||||||
|
notify.Dispatch('success', 'clipboard', i18n._(t`Data copied to clipboard`));
|
||||||
|
} else {
|
||||||
|
notify.Dispatch('error', 'clipboard', i18n._(t`Error while copying data to clipboard`));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button {...other} endIcon={<FileCopyIcon />} onClick={handleCopy}>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
CopyButton.defaultProps = {
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
@ -5,10 +5,12 @@ import { Trans, t } from '@lingui/macro';
|
|||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Link from '@mui/material/Link';
|
||||||
|
|
||||||
import * as Encoders from './coders/Encoders';
|
import * as Encoders from './coders/Encoders';
|
||||||
import * as Decoders from './coders/Decoders';
|
import * as Decoders from './coders/Decoders';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
|
import H from '../utils/help';
|
||||||
|
|
||||||
export default function EncodingSelect(props) {
|
export default function EncodingSelect(props) {
|
||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
@ -75,6 +77,11 @@ export default function EncodingSelect(props) {
|
|||||||
props.onChange(encoder, profile.decoder, automatic);
|
props.onChange(encoder, profile.decoder, automatic);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleEncoderHelp = (topic) => (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
H('encoder-' + topic);
|
||||||
|
};
|
||||||
|
|
||||||
let stream = null;
|
let stream = null;
|
||||||
|
|
||||||
if (profile.stream >= 0) {
|
if (profile.stream >= 0) {
|
||||||
@ -106,12 +113,17 @@ export default function EncodingSelect(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let encoderSettings = null;
|
let encoderSettings = null;
|
||||||
|
let encoderSettingsHelp = null;
|
||||||
|
|
||||||
let coder = encoderRegistry.Get(profile.encoder.coder);
|
let coder = encoderRegistry.Get(profile.encoder.coder);
|
||||||
if (coder !== null && props.availableEncoders.includes(coder.coder)) {
|
if (coder !== null && props.availableEncoders.includes(coder.coder)) {
|
||||||
const Settings = coder.component;
|
const Settings = coder.component;
|
||||||
|
|
||||||
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} onChange={handleEncoderSettingsChange} />;
|
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} onChange={handleEncoderSettingsChange} />;
|
||||||
|
|
||||||
|
if (props.type === 'video' && !['copy', 'none', 'rawvideo'].includes(coder.coder)) {
|
||||||
|
encoderSettingsHelp = handleEncoderHelp(coder.coder);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoderList = [];
|
let encoderList = [];
|
||||||
@ -207,6 +219,15 @@ export default function EncodingSelect(props) {
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{encoderSettings}
|
{encoderSettings}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
{encoderSettingsHelp !== null && (
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Trans>
|
||||||
|
<Link color="secondary" href="#" onClick={encoderSettingsHelp}>
|
||||||
|
Compatibility list
|
||||||
|
</Link>
|
||||||
|
</Trans>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,14 +14,7 @@ export default function Component(props) {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button variant="outlined" size="large" fullWidth color="primary" className={classes.button} {...props}>
|
||||||
variant="outlined"
|
|
||||||
size="large"
|
|
||||||
fullWidth
|
|
||||||
color="primary"
|
|
||||||
className={classes.button}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -48,6 +48,7 @@ export default function LanguageSelect(props) {
|
|||||||
<MenuItem value="it">Italiano </MenuItem>
|
<MenuItem value="it">Italiano </MenuItem>
|
||||||
<MenuItem value="pt">Português </MenuItem>
|
<MenuItem value="pt">Português </MenuItem>
|
||||||
<MenuItem value="es">Español </MenuItem>
|
<MenuItem value="es">Español </MenuItem>
|
||||||
|
<MenuItem value="ru">Русский </MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
float: 'right',
|
float: 'right',
|
||||||
marginLeft: '.5em',
|
marginLeft: '.5em',
|
||||||
paddingTop: '.25em',
|
paddingTop: '.25em',
|
||||||
marginRight: '-.7em',
|
marginRight: '-.7em',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
modalFooter: {
|
modalFooter: {
|
||||||
@ -53,19 +53,9 @@ const Component = React.forwardRef((props, ref) => {
|
|||||||
<Paper className={classes.modalPaper} elevation={0} tabIndex={-1} ref={ref} {...other}>
|
<Paper className={classes.modalPaper} elevation={0} tabIndex={-1} ref={ref} {...other}>
|
||||||
<Grid container spacing={0}>
|
<Grid container spacing={0}>
|
||||||
<Grid item xs={12} className={classes.modalHeader}>
|
<Grid item xs={12} className={classes.modalHeader}>
|
||||||
<Stack
|
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||||
direction="row"
|
|
||||||
justifyContent="space-between"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
<Typography variant="button">{props.title}</Typography>
|
<Typography variant="button">{props.title}</Typography>
|
||||||
<Stack
|
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={2}>
|
||||||
direction="row"
|
|
||||||
justifyContent="flex-end"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
{typeof props.onHelp === 'function' && (
|
{typeof props.onHelp === 'function' && (
|
||||||
<IconButton color="inherit" size="small" onClick={props.onHelp}>
|
<IconButton color="inherit" size="small" onClick={props.onHelp}>
|
||||||
<HelpIcon fontSize="small" />
|
<HelpIcon fontSize="small" />
|
||||||
|
|||||||
@ -25,14 +25,14 @@ const Component = React.forwardRef((props, ref) => {
|
|||||||
elevation = 0;
|
elevation = 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container justifyContent="center" spacing={1} style={{ marginBottom: props.marginBottom }}>
|
<Grid container justifyContent="center" spacing={1} style={{ marginBottom: props.marginBottom }}>
|
||||||
<Grid item xs={props.xs} sm={props.sm} md={props.md} lg={props.lg}>
|
<Grid item xs={props.xs} sm={props.sm} md={props.md} lg={props.lg}>
|
||||||
<Paper className={classes[props.className]} elevation={elevation} ref={ref} {...other}>
|
<Paper className={classes[props.className]} elevation={elevation} ref={ref} {...other}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Paper>
|
</Paper>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Component;
|
export default Component;
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import Grid from '@mui/material/Grid';
|
|||||||
|
|
||||||
const Component = function (props) {
|
const Component = function (props) {
|
||||||
return (
|
return (
|
||||||
<Grid container justifyContent="center" spacing={props.spacing} align={props.textAlign}>
|
<Grid container justifyContent="center" spacing={props.spacing} align={props.textAlign}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Component;
|
export default Component;
|
||||||
|
|||||||
@ -57,7 +57,7 @@ Component.defaultProps = {
|
|||||||
spacing: 0,
|
spacing: 0,
|
||||||
padding: null,
|
padding: null,
|
||||||
title: '',
|
title: '',
|
||||||
variant: 'pagetitel',
|
variant: 'pagetitle',
|
||||||
onAbort: null,
|
onAbort: null,
|
||||||
onHelp: null,
|
onHelp: null,
|
||||||
onEdit: null,
|
onEdit: null,
|
||||||
|
|||||||
@ -13,11 +13,11 @@ const useStyles = makeStyles((theme) => ({
|
|||||||
export default function Component(props) {
|
export default function Component(props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
return <CardMedia className={classes.media} style={{height: props.height}} image={props.image} title={props.title} />;
|
return <CardMedia className={classes.media} style={{ height: props.height }} image={props.image} title={props.title} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.defaultProps = {
|
Component.defaultProps = {
|
||||||
image: '',
|
image: '',
|
||||||
title: '',
|
title: '',
|
||||||
height: '0px'
|
height: '0px',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -54,9 +54,7 @@ export default function Password(props) {
|
|||||||
label={props.label}
|
label={props.label}
|
||||||
autoComplete={props.autoComplete}
|
autoComplete={props.autoComplete}
|
||||||
/>
|
/>
|
||||||
{props.helperText && (
|
{props.helperText && <FormHelperText>{props.helperText}</FormHelperText>}
|
||||||
<FormHelperText>{props.helperText}</FormHelperText>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
.vjs-internal {
|
.vjs-internal {
|
||||||
--video-js--primary: #EAEA05;
|
--video-js--primary: #eaea05;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* play-button */
|
/* play-button */
|
||||||
.vjs-internal .vjs-big-play-button {
|
.vjs-internal .vjs-big-play-button {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background: none;
|
background: none;
|
||||||
line-height: 180px;
|
line-height: 180px;
|
||||||
font-size: 180px;
|
font-size: 180px;
|
||||||
border: none;
|
border: none;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -90px;
|
margin-top: -90px;
|
||||||
color: rgba(255,255,255,.65);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal:hover .vjs-big-play-button,
|
.vjs-internal:hover .vjs-big-play-button,
|
||||||
.vjs-internal.vjs-big-play-button:focus {
|
.vjs-internal.vjs-big-play-button:focus {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgba(255,255,255,1);
|
color: rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* controlbar */
|
/* controlbar */
|
||||||
@ -27,67 +27,78 @@
|
|||||||
.vjs-internal .vjs-control-bar {
|
.vjs-internal .vjs-control-bar {
|
||||||
top: calc(50% - 45px);
|
top: calc(50% - 45px);
|
||||||
height: 100px;
|
height: 100px;
|
||||||
width: 50px;
|
width: 50px;
|
||||||
background-color: rgb(77, 77, 77);
|
background-color: rgb(77, 77, 77);
|
||||||
border-top-right-radius: 4px;
|
border-top-right-radius: 4px;
|
||||||
border-bottom-right-radius: 4px;
|
border-bottom-right-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-button>.vjs-icon-placeholder:before {
|
.vjs-internal .vjs-button > .vjs-icon-placeholder:before {
|
||||||
line-height: 50px
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* progressbar */
|
/* progressbar */
|
||||||
|
|
||||||
.vjs-internal .vjs-play-progress:before {
|
.vjs-internal .vjs-play-progress:before {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-progress-control {
|
.vjs-internal .vjs-progress-control {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* buttons */
|
/* buttons */
|
||||||
|
|
||||||
.vjs-internal .vjs-playing, .vjs-internal .vjs-paused {
|
.vjs-internal .vjs-playing,
|
||||||
|
.vjs-internal .vjs-paused {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: rgb(77, 77, 77);
|
background-color: rgb(77, 77, 77);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-volume-panel {
|
.vjs-internal .vjs-volume-panel {
|
||||||
width: 50px;
|
width: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
background-color: rgb(77, 77, 77);
|
background-color: rgb(77, 77, 77);
|
||||||
margin: 50px 0px 0px -50px;
|
margin: 50px 0px 0px -50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
transition: visibility 1s,opacity 1s,height 1s 1s,width 1s,right 1s 1s,top 1s 1s;
|
transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, right 1s 1s,
|
||||||
|
top 1s 1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-volume-panel.vjs-hover .vjs-mute-control~.vjs-volume-control, .vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control, .vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active, .vjs-internal .vjs-volume-panel .vjs-volume-control:active, .vjs-internal .vjs-volume-panel:active .vjs-volume-control, .vjs-internal .vjs-volume-panel:focus .vjs-volume-control {
|
.vjs-internal
|
||||||
visibility: visible;
|
.vjs-volume-panel.vjs-hover
|
||||||
opacity: 1;
|
.vjs-mute-control
|
||||||
position: relative;
|
~ .vjs-volume-control,
|
||||||
transition: visibility .1s,opacity .1s,height .1s,width .1s,right 0s,top 0s;
|
.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,
|
||||||
|
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,
|
||||||
|
.vjs-internal .vjs-volume-panel .vjs-volume-control:active,
|
||||||
|
.vjs-internal .vjs-volume-panel:active .vjs-volume-control,
|
||||||
|
.vjs-internal .vjs-volume-panel:focus .vjs-volume-control {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
position: relative;
|
||||||
|
transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, right 0s,
|
||||||
|
top 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
||||||
top: -0.4em;
|
top: -0.4em;
|
||||||
right: -0.5em;
|
right: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-remaining-time {
|
.vjs-internal .vjs-remaining-time {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-live-display {
|
.vjs-internal .vjs-live-display {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-picture-in-picture-control {
|
.vjs-internal .vjs-picture-in-picture-control {
|
||||||
|
|||||||
99
src/misc/Player/video-js-skin-internal.min.css
vendored
99
src/misc/Player/video-js-skin-internal.min.css
vendored
@ -1 +1,98 @@
|
|||||||
.vjs-internal{--video-js--primary:#EAEA05}.vjs-internal .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;color:rgba(255,255,255,.65)}.vjs-internal.vjs-big-play-button:focus,.vjs-internal:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-internal .vjs-control-bar{top:calc(50% - 45px);height:100px;width:50px;background-color:#4d4d4d;border-top-right-radius:4px;border-bottom-right-radius:4px}.vjs-internal .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-internal .vjs-play-progress:before{display:none}.vjs-internal .vjs-progress-control{display:none}.vjs-internal .vjs-paused,.vjs-internal .vjs-playing{width:50px;height:50px;border-radius:4px;background-color:#4d4d4d}.vjs-internal .vjs-volume-panel{width:50px;height:50px;border-radius:4px;padding-left:4px;background-color:#4d4d4d;margin:50px 0 0 -50px}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,right 1s 1s,top 1s 1s}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.vjs-internal .vjs-volume-panel .vjs-volume-control:active,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-mute-control~.vjs-volume-control,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,.vjs-internal .vjs-volume-panel:active .vjs-volume-control,.vjs-internal .vjs-volume-panel:focus .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,right 0s,top 0s}.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before{top:-.4em;right:-.5em}.vjs-internal .vjs-remaining-time{display:none}.vjs-internal .vjs-live-display{display:none}.vjs-internal .vjs-picture-in-picture-control{display:none}.vjs-internal .vjs-fullscreen-control{display:none}.vjs-internal .vjs-seek-to-live-control{display:none}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-internal .vjs-custom-control-spacer{display:block;width:100%}
|
.vjs-internal {
|
||||||
|
--video-js--primary: #eaea05;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-big-play-button {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background: 0 0;
|
||||||
|
line-height: 180px;
|
||||||
|
font-size: 180px;
|
||||||
|
border: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -90px;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
.vjs-internal.vjs-big-play-button:focus,
|
||||||
|
.vjs-internal:hover .vjs-big-play-button {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-control-bar {
|
||||||
|
top: calc(50% - 45px);
|
||||||
|
height: 100px;
|
||||||
|
width: 50px;
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-button > .vjs-icon-placeholder:before {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-play-progress:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-progress-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-paused,
|
||||||
|
.vjs-internal .vjs-playing {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-volume-panel {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding-left: 4px;
|
||||||
|
background-color: #4d4d4d;
|
||||||
|
margin: 50px 0 0 -50px;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
|
padding-top: 1em;
|
||||||
|
transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, right 1s 1s,
|
||||||
|
top 1s 1s;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,
|
||||||
|
.vjs-internal .vjs-volume-panel .vjs-volume-control:active,
|
||||||
|
.vjs-internal
|
||||||
|
.vjs-volume-panel.vjs-hover
|
||||||
|
.vjs-mute-control
|
||||||
|
~ .vjs-volume-control,
|
||||||
|
.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,
|
||||||
|
.vjs-internal .vjs-volume-panel:active .vjs-volume-control,
|
||||||
|
.vjs-internal .vjs-volume-panel:focus .vjs-volume-control {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
position: relative;
|
||||||
|
transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, right 0s,
|
||||||
|
top 0s;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
||||||
|
top: -0.4em;
|
||||||
|
right: -0.5em;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-remaining-time {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-live-display {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-picture-in-picture-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-fullscreen-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-seek-to-live-control {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-subs-caps-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-custom-control-spacer {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,111 +1,111 @@
|
|||||||
.vjs-public {
|
.vjs-public {
|
||||||
--video-js--primary: #EAEA05;
|
--video-js--primary: #eaea05;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* play btn */
|
/* play btn */
|
||||||
|
|
||||||
.vjs-public .vjs-big-play-button {
|
.vjs-public .vjs-big-play-button {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background: none;
|
background: none;
|
||||||
line-height: 180px;
|
line-height: 180px;
|
||||||
font-size: 180px;
|
font-size: 180px;
|
||||||
border: none;
|
border: none;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -90px;
|
margin-top: -90px;
|
||||||
margin-left: -90px;
|
margin-left: -90px;
|
||||||
color: rgba(255,255,255,.65);
|
color: rgba(255, 255, 255, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public:hover .vjs-big-play-button,
|
.vjs-public:hover .vjs-big-play-button,
|
||||||
.vjs-public.vjs-big-play-button:focus {
|
.vjs-public.vjs-big-play-button:focus {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgba(255,255,255,1);
|
color: rgba(255, 255, 255, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* controlbar */
|
/* controlbar */
|
||||||
|
|
||||||
.vjs-public .vjs-control-bar {
|
.vjs-public .vjs-control-bar {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
background: none;
|
background: none;
|
||||||
background-image: linear-gradient(0deg, rgba(0,0,0,.85), transparent)
|
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-time-tooltip {
|
.vjs-public .vjs-time-tooltip {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-button>.vjs-icon-placeholder:before {
|
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
|
||||||
line-height: 50px
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* progressbar */
|
/* progressbar */
|
||||||
|
|
||||||
.vjs-public .vjs-play-progress:before {
|
.vjs-public .vjs-play-progress:before {
|
||||||
display: none
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-progress-control {
|
.vjs-public .vjs-progress-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
height: 20px
|
height: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-play-progress {
|
.vjs-public .vjs-play-progress {
|
||||||
background-color: var(--video-js--primary);
|
background-color: var(--video-js--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-slider {
|
.vjs-public .vjs-slider {
|
||||||
background: rgba(255,255,255,.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-load-progress {
|
.vjs-public .vjs-load-progress {
|
||||||
background: rgba(255,255,255,.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-load-progress div {
|
.vjs-public .vjs-load-progress div {
|
||||||
background: rgba(255,255,255,.25);
|
background: rgba(255, 255, 255, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-remaining-time {
|
.vjs-public .vjs-remaining-time {
|
||||||
order: 0;
|
order: 0;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
flex: 3;
|
flex: 3;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-live-control {
|
.vjs-public .vjs-live-control {
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* volume-panel */
|
/* volume-panel */
|
||||||
|
|
||||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
padding-top: 1em;
|
padding-top: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-control .vjs-volume-panel {
|
.vjs-public .vjs-control .vjs-volume-panel {
|
||||||
width: 4.5em;
|
width: 4.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* live display */
|
/* live display */
|
||||||
|
|
||||||
.vjs-public .vjs-live-display {
|
.vjs-public .vjs-live-display {
|
||||||
margin-left: 1.8em;
|
margin-left: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* disable caps */
|
/* disable caps */
|
||||||
@ -128,43 +128,42 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-no-background {
|
.vjs-public .vjs-overlay-no-background {
|
||||||
max-width: 28%!important;
|
max-width: 28% !important;
|
||||||
max-height: 28%!important;
|
max-height: 28% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-left {
|
.vjs-public .vjs-overlay-top-left {
|
||||||
top: 20px!important;
|
top: 20px !important;
|
||||||
left: 30px!important;
|
left: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-right {
|
.vjs-public .vjs-overlay-top-right {
|
||||||
top: 20px!important;
|
top: 20px !important;
|
||||||
right: 30px!important;
|
right: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-left {
|
.vjs-public .vjs-overlay-bottom-left {
|
||||||
bottom: 20px!important;
|
bottom: 20px !important;
|
||||||
left: 30px!important;
|
left: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-right {
|
.vjs-public .vjs-overlay-bottom-right {
|
||||||
bottom: 20px!important;
|
bottom: 20px !important;
|
||||||
right: 30px!important;
|
right: 30px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* context menu */
|
/* context menu */
|
||||||
|
|
||||||
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
||||||
background: rgba(0,0,0,.8);
|
background: rgba(0, 0, 0, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-license-top-level-header {
|
.vjs-public .vjs-license-top-level-header {
|
||||||
background: unset!important;
|
background: unset !important;
|
||||||
border-bottom: 1px solid rgba(255,255,255,.25);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-lock-open {
|
.vjs-public .vjs-lock-open {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
124
src/misc/Player/video-js-skin-public.min.css
vendored
124
src/misc/Player/video-js-skin-public.min.css
vendored
@ -1 +1,123 @@
|
|||||||
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public.vjs-big-play-button:focus,.vjs-public:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:0 0;background-image:linear-gradient(0deg,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay>a>img{width:100%}.vjs-public .vjs-overlay-no-background{max-width:28%!important;max-height:28%!important}.vjs-public .vjs-overlay-top-left{top:20px!important;left:30px!important}.vjs-public .vjs-overlay-top-right{top:20px!important;right:30px!important}.vjs-public .vjs-overlay-bottom-left{bottom:20px!important;left:30px!important}.vjs-public .vjs-overlay-bottom-right{bottom:20px!important;right:30px!important}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25);min-width:100px}.vjs-public .vjs-lock-open{z-index:1000}
|
.vjs-public {
|
||||||
|
--video-js--primary: #eaea05;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-big-play-button {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
background: 0 0;
|
||||||
|
line-height: 180px;
|
||||||
|
font-size: 180px;
|
||||||
|
border: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -90px;
|
||||||
|
margin-left: -90px;
|
||||||
|
color: rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
.vjs-public.vjs-big-play-button:focus,
|
||||||
|
.vjs-public:hover .vjs-big-play-button {
|
||||||
|
background-color: transparent;
|
||||||
|
color: rgba(255, 255, 255, 1);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-control-bar {
|
||||||
|
height: 70px;
|
||||||
|
padding-top: 20px;
|
||||||
|
background: 0 0;
|
||||||
|
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-time-tooltip {
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-play-progress:before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-progress-control {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 15px;
|
||||||
|
width: calc(100% - 30px);
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-play-progress {
|
||||||
|
background-color: var(--video-js--primary);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-slider {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-load-progress {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-load-progress div {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-remaining-time {
|
||||||
|
order: 0;
|
||||||
|
line-height: 50px;
|
||||||
|
flex: 3;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-live-control {
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
|
padding-top: 1em;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-control .vjs-volume-panel {
|
||||||
|
width: 4.5em;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-live-display {
|
||||||
|
margin-left: 1.8em;
|
||||||
|
}
|
||||||
|
.vjs-internal .vjs-subs-caps-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-custom-control-spacer {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay > a > img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay-no-background {
|
||||||
|
max-width: 28% !important;
|
||||||
|
max-height: 28% !important;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay-top-left {
|
||||||
|
top: 20px !important;
|
||||||
|
left: 30px !important;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay-top-right {
|
||||||
|
top: 20px !important;
|
||||||
|
right: 30px !important;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay-bottom-left {
|
||||||
|
bottom: 20px !important;
|
||||||
|
left: 30px !important;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-overlay-bottom-right {
|
||||||
|
bottom: 20px !important;
|
||||||
|
right: 30px !important;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-license-top-level-header {
|
||||||
|
background: unset !important;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.vjs-public .vjs-lock-open {
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import makeStyles from '@mui/styles/makeStyles';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
'& .MuiBox-root': {
|
'& .MuiBox-root': {
|
||||||
padding: 0,
|
padding: 0,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function TabPanel(props) {
|
export default function TabPanel(props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
@ -19,7 +19,11 @@ export default function TabPanel(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes.root} role="tabpanel" hidden={value !== index} id={`vertical-tabpanel-${index}`} {...other}>
|
<div className={classes.root} role="tabpanel" hidden={value !== index} id={`vertical-tabpanel-${index}`} {...other}>
|
||||||
{value === index && <Box classes={{ root: classes }} p={0}>{children}</Box>}
|
{value === index && (
|
||||||
|
<Box classes={{ root: classes }} p={0}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,9 +34,7 @@ export default function Component(props) {
|
|||||||
rows={props.rows}
|
rows={props.rows}
|
||||||
type={props.type}
|
type={props.type}
|
||||||
/>
|
/>
|
||||||
{props.helperText && (
|
{props.helperText && <FormHelperText>{props.helperText}</FormHelperText>}
|
||||||
<FormHelperText>{props.helperText}</FormHelperText>
|
|
||||||
)}
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@ import InputLabel from '@mui/material/InputLabel';
|
|||||||
import NotifyContext from '../contexts/Notify';
|
import NotifyContext from '../contexts/Notify';
|
||||||
import OutlinedInput from '@mui/material/OutlinedInput';
|
import OutlinedInput from '@mui/material/OutlinedInput';
|
||||||
|
|
||||||
|
import CopyToClipboard from '../utils/clipboard';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
root: {
|
root: {
|
||||||
'& .MuiOutlinedInput-notchedOutline': {
|
'& .MuiOutlinedInput-notchedOutline': {
|
||||||
@ -23,22 +25,9 @@ export default function Component(props) {
|
|||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
|
|
||||||
const notify = useContext(NotifyContext);
|
const notify = useContext(NotifyContext);
|
||||||
const textAreaRef = React.createRef();
|
|
||||||
|
|
||||||
const handleCopy = async () => {
|
const handleCopy = async () => {
|
||||||
let success = false;
|
const success = await CopyToClipboard(props.value);
|
||||||
|
|
||||||
if (!navigator.clipboard) {
|
|
||||||
textAreaRef.current.select();
|
|
||||||
|
|
||||||
try {
|
|
||||||
success = document.execCommand('copy');
|
|
||||||
} catch (err) {}
|
|
||||||
|
|
||||||
textAreaRef.current.setSelectionRange(0, 0);
|
|
||||||
} else {
|
|
||||||
success = await writeText(navigator.clipboard.writeText(props.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (success === true) {
|
if (success === true) {
|
||||||
notify.Dispatch('success', 'clipboard', i18n._(t`Data copied to clipboard`));
|
notify.Dispatch('success', 'clipboard', i18n._(t`Data copied to clipboard`));
|
||||||
@ -47,21 +36,11 @@ export default function Component(props) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const writeText = (promise) => {
|
|
||||||
return promise
|
|
||||||
.then(() => true)
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn(err);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl variant="outlined" disabled={props.disabled} fullWidth>
|
<FormControl variant="outlined" disabled={props.disabled} fullWidth>
|
||||||
<InputLabel htmlFor={props.id}>{props.label}</InputLabel>
|
<InputLabel htmlFor={props.id}>{props.label}</InputLabel>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
className={classes.root}
|
className={classes.root}
|
||||||
inputRef={textAreaRef}
|
|
||||||
id={props.id}
|
id={props.id}
|
||||||
value={props.value}
|
value={props.value}
|
||||||
label={props.label}
|
label={props.label}
|
||||||
|
|||||||
@ -99,7 +99,7 @@ export default function Component(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let textAreaDivStyle = {
|
let textAreaDivStyle = {
|
||||||
width: '100%'
|
width: '100%',
|
||||||
};
|
};
|
||||||
let textAreaStyle = {
|
let textAreaStyle = {
|
||||||
lineHeight: 1.3,
|
lineHeight: 1.3,
|
||||||
@ -116,7 +116,7 @@ export default function Component(props) {
|
|||||||
if (props.rows === 1) {
|
if (props.rows === 1) {
|
||||||
textAreaStyle = {
|
textAreaStyle = {
|
||||||
...textAreaStyle,
|
...textAreaStyle,
|
||||||
height: ((18 * props.rows) + 9.5) + 'px',
|
height: 18 * props.rows + 9.5 + 'px',
|
||||||
overflowY: 'hidden',
|
overflowY: 'hidden',
|
||||||
marginBottom: '0em',
|
marginBottom: '0em',
|
||||||
marginTop: '0em',
|
marginTop: '0em',
|
||||||
@ -134,7 +134,7 @@ export default function Component(props) {
|
|||||||
} else {
|
} else {
|
||||||
textAreaStyle = {
|
textAreaStyle = {
|
||||||
...textAreaStyle,
|
...textAreaStyle,
|
||||||
height: ((18 * props.rows) + 8) + 'px',
|
height: 18 * props.rows + 8 + 'px',
|
||||||
};
|
};
|
||||||
textAreaDivStyle = {
|
textAreaDivStyle = {
|
||||||
...textAreaDivStyle,
|
...textAreaDivStyle,
|
||||||
@ -149,20 +149,8 @@ export default function Component(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Stack
|
<Stack direction="column" justifyContent="center" alignItems="flex-start" spacing={1} style={textAreaDivStyle}>
|
||||||
direction="column"
|
<Stack direction="column" justifyContent="flex-start" alignItems="flex-end" spacing={0} width="100%">
|
||||||
justifyContent="center"
|
|
||||||
alignItems="flex-start"
|
|
||||||
spacing={1}
|
|
||||||
style={textAreaDivStyle}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
direction="column"
|
|
||||||
justifyContent="flex-start"
|
|
||||||
alignItems="flex-end"
|
|
||||||
spacing={0}
|
|
||||||
width="100%"
|
|
||||||
>
|
|
||||||
{allowCopy && (
|
{allowCopy && (
|
||||||
<IconButton size="small" onClick={handleCopy} style={actionButton}>
|
<IconButton size="small" onClick={handleCopy} style={actionButton}>
|
||||||
<FileCopyIcon fontSize="small" />
|
<FileCopyIcon fontSize="small" />
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import * as X265 from './video/X265';
|
|||||||
import * as H264VideoToolbox from './video/H264VideoToolbox';
|
import * as H264VideoToolbox from './video/H264VideoToolbox';
|
||||||
import * as H264NVENC from './video/H264NVENC';
|
import * as H264NVENC from './video/H264NVENC';
|
||||||
import * as H264OMX from './video/H264OMX';
|
import * as H264OMX from './video/H264OMX';
|
||||||
|
import * as H264V4L2M2M from './video/H264V4L2M2M';
|
||||||
|
import * as H264VAAPI from './video/H264VAAPI';
|
||||||
import * as VideoCopy from './video/Copy';
|
import * as VideoCopy from './video/Copy';
|
||||||
import * as VideoNone from './video/None';
|
import * as VideoNone from './video/None';
|
||||||
import * as VideoRaw from './video/Raw';
|
import * as VideoRaw from './video/Raw';
|
||||||
@ -123,6 +125,8 @@ videoRegistry.Register(X265);
|
|||||||
videoRegistry.Register(H264VideoToolbox);
|
videoRegistry.Register(H264VideoToolbox);
|
||||||
videoRegistry.Register(H264NVENC);
|
videoRegistry.Register(H264NVENC);
|
||||||
videoRegistry.Register(H264OMX);
|
videoRegistry.Register(H264OMX);
|
||||||
|
videoRegistry.Register(H264V4L2M2M);
|
||||||
|
videoRegistry.Register(H264VAAPI);
|
||||||
videoRegistry.Register(VP9);
|
videoRegistry.Register(VP9);
|
||||||
videoRegistry.Register(VideoRaw);
|
videoRegistry.Register(VideoRaw);
|
||||||
|
|
||||||
|
|||||||
163
src/misc/coders/Encoders/video/H264V4L2M2M.js
Normal file
163
src/misc/coders/Encoders/video/H264V4L2M2M.js
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
|
||||||
|
import Video from '../../settings/Video';
|
||||||
|
|
||||||
|
function init(initialState) {
|
||||||
|
const state = {
|
||||||
|
bitrate: '4096',
|
||||||
|
fps: '25',
|
||||||
|
gop: '2',
|
||||||
|
profile: 'auto',
|
||||||
|
...initialState,
|
||||||
|
};
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://forums.raspberrypi.com/viewtopic.php?t=294161
|
||||||
|
// ffmpeg -y -nostdin -f v4l2 -threads auto -input_format yuyv422 -fflags +genpts -flags +global_header \
|
||||||
|
// -i /dev/video0 -s 1280x720 -r 25 \
|
||||||
|
// -vcodec h264_v4l2m2m \
|
||||||
|
// -num_output_buffers 32 -num_capture_buffers 16 \
|
||||||
|
// -keyint_min 25 -force_key_frames "expr:gte(t,n_forced*1)" \
|
||||||
|
// -g 50 -b:v 6M -pix_fmt nv12 \
|
||||||
|
// -f mp4 -f segment -segment_format_options movflags=+faststart -segment_time 1 -reset_timestamps 1 -segment_time 1 -segment_time_delta 1 -segment_format mp4 -muxdelay 0 -muxpreload 0 /home/pi/Videos/%d-output.mp4
|
||||||
|
|
||||||
|
/**
|
||||||
|
v4l2-ctl --list-ctrls-menu -d 11
|
||||||
|
|
||||||
|
Codec Controls
|
||||||
|
|
||||||
|
video_bitrate_mode 0x009909ce (menu) : min=0 max=1 default=0 value=0 (Variable Bitrate) flags=update
|
||||||
|
0: Variable Bitrate
|
||||||
|
1: Constant Bitrate
|
||||||
|
video_bitrate 0x009909cf (int) : min=25000 max=25000000 step=25000 default=10000000 value=10000000
|
||||||
|
sequence_header_mode 0x009909d8 (menu) : min=0 max=1 default=1 value=1 (Joined With 1st Frame)
|
||||||
|
0: Separate Buffer
|
||||||
|
1: Joined With 1st Frame
|
||||||
|
repeat_sequence_header 0x009909e2 (bool) : default=0 value=0
|
||||||
|
force_key_frame 0x009909e5 (button) : value=0 flags=write-only, execute-on-write
|
||||||
|
h264_minimum_qp_value 0x00990a61 (int) : min=0 max=51 step=1 default=20 value=20
|
||||||
|
h264_maximum_qp_value 0x00990a62 (int) : min=0 max=51 step=1 default=51 value=51
|
||||||
|
h264_i_frame_period 0x00990a66 (int) : min=0 max=2147483647 step=1 default=60 value=60
|
||||||
|
h264_level 0x00990a67 (menu) : min=0 max=13 default=11 value=11 (4)
|
||||||
|
0: 1
|
||||||
|
1: 1b
|
||||||
|
2: 1.1
|
||||||
|
3: 1.2
|
||||||
|
4: 1.3
|
||||||
|
5: 2
|
||||||
|
6: 2.1
|
||||||
|
7: 2.2
|
||||||
|
8: 3
|
||||||
|
9: 3.1
|
||||||
|
10: 3.2
|
||||||
|
11: 4
|
||||||
|
12: 4.1
|
||||||
|
13: 4.2
|
||||||
|
h264_profile 0x00990a6b (menu) : min=0 max=4 default=4 value=4 (High)
|
||||||
|
0: Baseline
|
||||||
|
1: Constrained Baseline
|
||||||
|
2: Main
|
||||||
|
4: High
|
||||||
|
*/
|
||||||
|
|
||||||
|
function createMapping(settings) {
|
||||||
|
const mapping = [
|
||||||
|
'-codec:v',
|
||||||
|
'h264_v4l2m2m',
|
||||||
|
'-b:v',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-maxrate',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-bufsize',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-r',
|
||||||
|
`${settings.fps}`,
|
||||||
|
'-pix_fmt',
|
||||||
|
'yuv420p',
|
||||||
|
'-vsync',
|
||||||
|
'1',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (settings.gop !== 'auto') {
|
||||||
|
mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.profile !== 'auto') {
|
||||||
|
mapping.push('-profile:v', `${settings.profile}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Coder(props) {
|
||||||
|
const settings = init(props.settings);
|
||||||
|
|
||||||
|
const handleChange = (newSettings) => {
|
||||||
|
let automatic = false;
|
||||||
|
if (!newSettings) {
|
||||||
|
newSettings = settings;
|
||||||
|
automatic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = (what) => (event) => {
|
||||||
|
const newSettings = {
|
||||||
|
...settings,
|
||||||
|
[what]: event.target.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange(newSettings);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
handleChange(null);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Video.Framerate value={settings.fps} onChange={update('fps')} allowCustom />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Coder.defaultProps = {
|
||||||
|
stream: {},
|
||||||
|
settings: {},
|
||||||
|
onChange: function (settings, mapping) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const coder = 'h264_v4l2m2m';
|
||||||
|
const name = 'H.264 (V4L2 Memory to Memory)';
|
||||||
|
const codec = 'h264';
|
||||||
|
const type = 'video';
|
||||||
|
const hwaccel = true;
|
||||||
|
|
||||||
|
function summarize(settings) {
|
||||||
|
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaults() {
|
||||||
|
const settings = init({});
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings: settings,
|
||||||
|
mapping: createMapping(settings),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as component };
|
||||||
171
src/misc/coders/Encoders/video/H264VAAPI.js
Normal file
171
src/misc/coders/Encoders/video/H264VAAPI.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
|
||||||
|
import Select from '../../../Select';
|
||||||
|
import Video from '../../settings/Video';
|
||||||
|
|
||||||
|
function init(initialState) {
|
||||||
|
const state = {
|
||||||
|
bitrate: '4096',
|
||||||
|
fps: '25',
|
||||||
|
gop: '2',
|
||||||
|
profile: '77',
|
||||||
|
rc_mode: '1',
|
||||||
|
quality: '-1',
|
||||||
|
...initialState,
|
||||||
|
};
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMapping(settings) {
|
||||||
|
const mapping = [
|
||||||
|
'-vaapi_device',
|
||||||
|
'/dev/dri/renderD128',
|
||||||
|
'-vf',
|
||||||
|
'format=nv12,hwupload',
|
||||||
|
'-codec:v',
|
||||||
|
'h264_vaapi',
|
||||||
|
'-profile:v',
|
||||||
|
`${settings.profile}`,
|
||||||
|
'-quality',
|
||||||
|
`${settings.quality}`,
|
||||||
|
'-level',
|
||||||
|
`${settings.level}`,
|
||||||
|
'-b:v',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-maxrate',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-bufsize',
|
||||||
|
`${settings.bitrate}k`,
|
||||||
|
'-r',
|
||||||
|
`${settings.fps}`,
|
||||||
|
'-g',
|
||||||
|
`${settings.gop}`,
|
||||||
|
'-vsync',
|
||||||
|
'1',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (settings.gop !== 'auto') {
|
||||||
|
mapping.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RateControl(props) {
|
||||||
|
return (
|
||||||
|
<Select label={<Trans>Rate control</Trans>} value={props.value} onChange={props.onChange}>
|
||||||
|
<MenuItem value="0">auto</MenuItem>
|
||||||
|
<MenuItem value="1">Constant-quality</MenuItem>
|
||||||
|
<MenuItem value="2">Constant-bitrate</MenuItem>
|
||||||
|
<MenuItem value="3">Variable-bitrate</MenuItem>
|
||||||
|
<MenuItem value="4">Intelligent constant-quality</MenuItem>
|
||||||
|
<MenuItem value="5">Quality-defined variable-bitrate</MenuItem>
|
||||||
|
<MenuItem value="6">Average variable-bitrate</MenuItem>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
RateControl.defaultProps = {
|
||||||
|
value: '',
|
||||||
|
onChange: function (event) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function Profile(props) {
|
||||||
|
return (
|
||||||
|
<Select label={<Trans>Profile</Trans>} value={props.value} onChange={props.onChange}>
|
||||||
|
<MenuItem value="578">baseline (constrained)</MenuItem>
|
||||||
|
<MenuItem value="77">main</MenuItem>
|
||||||
|
<MenuItem value="100">high</MenuItem>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Profile.defaultProps = {
|
||||||
|
value: '',
|
||||||
|
onChange: function (event) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
function Coder(props) {
|
||||||
|
const settings = init(props.settings);
|
||||||
|
|
||||||
|
const handleChange = (newSettings) => {
|
||||||
|
let automatic = false;
|
||||||
|
if (!newSettings) {
|
||||||
|
newSettings = settings;
|
||||||
|
automatic = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||||
|
};
|
||||||
|
|
||||||
|
const update = (what) => (event) => {
|
||||||
|
const newSettings = {
|
||||||
|
...settings,
|
||||||
|
[what]: event.target.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleChange(newSettings);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
handleChange(null);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<RateControl value={settings.rc_mode} onChange={update('rc_mode')} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Video.Framerate value={settings.fps} onChange={update('fps')} allowCustom />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
|
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<TextField variant="outlined" fullWidth label={<Trans>Quality</Trans>} type="number" value={settings.quality} onChange={update('quality')} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={6}>
|
||||||
|
<Profile value={settings.profile} onChange={update('profile')} />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Coder.defaultProps = {
|
||||||
|
stream: {},
|
||||||
|
settings: {},
|
||||||
|
onChange: function (settings, mapping) {},
|
||||||
|
};
|
||||||
|
|
||||||
|
const coder = 'h264_vaapi';
|
||||||
|
const name = 'H.264 (Intel VAAPI)';
|
||||||
|
const codec = 'h264';
|
||||||
|
const type = 'video';
|
||||||
|
const hwaccel = true;
|
||||||
|
|
||||||
|
function summarize(settings) {
|
||||||
|
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function defaults() {
|
||||||
|
const settings = init({});
|
||||||
|
|
||||||
|
return {
|
||||||
|
settings: settings,
|
||||||
|
mapping: createMapping(settings),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as component };
|
||||||
@ -5,11 +5,14 @@ import Grid from '@mui/material/Grid';
|
|||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
|
import Checkbox from '../Checkbox';
|
||||||
|
|
||||||
function init(settings) {
|
function init(settings) {
|
||||||
const initSettings = {
|
const initSettings = {
|
||||||
lhls: false,
|
lhls: false,
|
||||||
segmentDuration: 2,
|
segmentDuration: 2,
|
||||||
listSize: 6,
|
listSize: 6,
|
||||||
|
cleanup: true,
|
||||||
...settings,
|
...settings,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,7 +31,7 @@ export default function Control(props) {
|
|||||||
const handleChange = (what) => (event) => {
|
const handleChange = (what) => (event) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
|
||||||
if (what === 'lhls') {
|
if (['lhls', 'cleanup'].includes(what)) {
|
||||||
settings[what] = !settings[what];
|
settings[what] = !settings[what];
|
||||||
} else {
|
} else {
|
||||||
settings[what] = value;
|
settings[what] = value;
|
||||||
@ -76,6 +79,9 @@ export default function Control(props) {
|
|||||||
<Trans>The maximum number of playlist segments. 0 will contain all the segments. 6 is recommended.</Trans>
|
<Trans>The maximum number of playlist segments. 0 will contain all the segments. 6 is recommended.</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Checkbox label={<Trans>Automatic cleanup of all media data</Trans>} checked={settings.cleanup} onChange={handleChange('cleanup')} />
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,10 +66,7 @@ export default function Control(props) {
|
|||||||
return (
|
return (
|
||||||
<Grid container spacing={0}>
|
<Grid container spacing={0}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TabsHorizontal
|
<TabsHorizontal value={$tab} onChange={handleChangeTab}>
|
||||||
value={$tab}
|
|
||||||
onChange={handleChangeTab}
|
|
||||||
>
|
|
||||||
<Tab className="tab" label={<Trans>Content</Trans>} value="content" />
|
<Tab className="tab" label={<Trans>Content</Trans>} value="content" />
|
||||||
<Tab className="tab" label={<Trans>Author</Trans>} value="author" />
|
<Tab className="tab" label={<Trans>Author</Trans>} value="author" />
|
||||||
</TabsHorizontal>
|
</TabsHorizontal>
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export default function Control(props) {
|
|||||||
const handleChange = (what) => (event) => {
|
const handleChange = (what) => (event) => {
|
||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
|
|
||||||
if (['autostart', 'reconnect'].includes(what)) {
|
if (['autostart', 'reconnect', 'cleanup'].includes(what)) {
|
||||||
settings[what] = !settings[what];
|
settings[what] = !settings[what];
|
||||||
} else {
|
} else {
|
||||||
settings[what] = value;
|
settings[what] = value;
|
||||||
|
|||||||
@ -43,7 +43,7 @@ export default function Control(props) {
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Checkbox label={<Trans>Enable snapshots</Trans>} checked={settings.enable} onChange={handleChange('enable')} />
|
<Checkbox label={<Trans>Enable snapshots</Trans>} checked={settings.enable} onChange={handleChange('enable')} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12} md={6}>
|
||||||
<TextField
|
<TextField
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
|||||||
@ -26,13 +26,7 @@ const Component = function (props) {
|
|||||||
<ModalContent title={props.title} onClose={props.onClose} onHelp={props.onHelp} style={{ overflow: 'hidden' }}>
|
<ModalContent title={props.title} onClose={props.onClose} onHelp={props.onHelp} style={{ overflow: 'hidden' }}>
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Stack
|
<Stack direction="column" justifyContent="center" alignItems="center" spacing={1} className={classes.box}>
|
||||||
direction="column"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={1}
|
|
||||||
className={classes.box}
|
|
||||||
>
|
|
||||||
<Textarea {...other} />
|
<Textarea {...other} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -20,7 +20,7 @@ export default {
|
|||||||
// MuiButton, MuiMenu, MuiToggleButton, MultiSelectOption.js (MenuItem)
|
// MuiButton, MuiMenu, MuiToggleButton, MultiSelectOption.js (MenuItem)
|
||||||
dark1: 'rgba(0, 0, 0, .1)',
|
dark1: 'rgba(0, 0, 0, .1)',
|
||||||
// MuiOutlinedInput, BoxText.js (color=dark)
|
// MuiOutlinedInput, BoxText.js (color=dark)
|
||||||
dark2: 'rgba(0, 0, 0, .25)',
|
dark2: 'rgba(0, 0, 0, .25)',
|
||||||
// Footer.js, Textarea.js, global.js (Scrollbar)
|
// Footer.js, Textarea.js, global.js (Scrollbar)
|
||||||
footer1: 'rgba(66, 61, 63, .9)',
|
footer1: 'rgba(66, 61, 63, .9)',
|
||||||
// Footer.js, Textarea.js
|
// Footer.js, Textarea.js
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable import/no-anonymous-default-export */
|
/* eslint-disable import/no-anonymous-default-export */
|
||||||
import palette from './palette';
|
import palette from './palette';
|
||||||
|
|
||||||
import "@fontsource/dosis/300.css"
|
import '@fontsource/dosis/300.css';
|
||||||
import "@fontsource/dosis/400.css"
|
import '@fontsource/dosis/400.css';
|
||||||
import "@fontsource/dosis/500.css"
|
import '@fontsource/dosis/500.css';
|
||||||
import "@fontsource/dosis/700.css"
|
import '@fontsource/dosis/700.css';
|
||||||
import "@fontsource/roboto/300.css"
|
import '@fontsource/roboto/300.css';
|
||||||
import "@fontsource/roboto/400.css"
|
import '@fontsource/roboto/400.css';
|
||||||
import "@fontsource/roboto/500.css"
|
import '@fontsource/roboto/500.css';
|
||||||
import "@fontsource/roboto/700.css"
|
import '@fontsource/roboto/700.css';
|
||||||
|
|
||||||
const base = {
|
const base = {
|
||||||
htmlFontSize: 16,
|
htmlFontSize: 16,
|
||||||
@ -74,7 +74,7 @@ const base = {
|
|||||||
button: {
|
button: {
|
||||||
fontSize: '.9rem',
|
fontSize: '.9rem',
|
||||||
},
|
},
|
||||||
pagetitel: {
|
pagetitle: {
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
textTransform: 'uppercase',
|
textTransform: 'uppercase',
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ const root = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const outlined = {
|
const outlined = {
|
||||||
|
|
||||||
base: {
|
base: {
|
||||||
color: base.palette.text.primary,
|
color: base.palette.text.primary,
|
||||||
backgroundColor: base.palette.background.dark1,
|
backgroundColor: base.palette.background.dark1,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export default {
|
|||||||
marginTop: '0.35em',
|
marginTop: '0.35em',
|
||||||
marginBottom: '0.35em',
|
marginBottom: '0.35em',
|
||||||
borderTop: `2px solid ${base.palette.text.disabled}`,
|
borderTop: `2px solid ${base.palette.text.disabled}`,
|
||||||
opacity: .6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,13 +6,13 @@ export default {
|
|||||||
html: {
|
html: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
fontSize: '16px/1.5'
|
fontSize: '16px/1.5',
|
||||||
},
|
},
|
||||||
body: {
|
body: {
|
||||||
background: `${base.palette.background.button_disabled} url(${universe}) no-repeat fixed left top`,
|
background: `${base.palette.background.button_disabled} url(${universe}) no-repeat fixed left top`,
|
||||||
backgroundSize: 'cover',
|
backgroundSize: 'cover',
|
||||||
overflowX: 'hidden',
|
overflowX: 'hidden',
|
||||||
overflowY: 'scroll'
|
overflowY: 'scroll',
|
||||||
},
|
},
|
||||||
code: {
|
code: {
|
||||||
fontFamily: 'soure-code-pro, monospace',
|
fontFamily: 'soure-code-pro, monospace',
|
||||||
@ -20,7 +20,8 @@ export default {
|
|||||||
textarea: {
|
textarea: {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
backgroundColor: base.palette.background.modalbox,
|
backgroundColor: base.palette.background.modalbox,
|
||||||
fontFamily: 'Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace',
|
fontFamily:
|
||||||
|
'Consolas, "Andale Mono WT", "Andale Mono", "Lucida Console", "Lucida Sans Typewriter", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Liberation Mono", "Nimbus Mono L", Monaco, "Courier New", Courier, monospace',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
whiteSpace: 'pre',
|
whiteSpace: 'pre',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
@ -35,7 +36,7 @@ export default {
|
|||||||
outline: 'none',
|
outline: 'none',
|
||||||
},
|
},
|
||||||
'::-webkit-scrollbar': {
|
'::-webkit-scrollbar': {
|
||||||
width:6,
|
width: 6,
|
||||||
height: 6,
|
height: 6,
|
||||||
},
|
},
|
||||||
'::-webkit-scrollbar:vertical': {
|
'::-webkit-scrollbar:vertical': {
|
||||||
|
|||||||
46
src/utils/clipboard.js
Normal file
46
src/utils/clipboard.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export default async function CopyToClipboard(content) {
|
||||||
|
let success = false;
|
||||||
|
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
success = writeTextDeprecated(content);
|
||||||
|
} else {
|
||||||
|
success = await writeText(navigator.clipboard.writeText(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
const writeText = (promise) => {
|
||||||
|
return promise
|
||||||
|
.then(() => true)
|
||||||
|
.catch((err) => {
|
||||||
|
console.warn(err);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const writeTextDeprecated = (value) => {
|
||||||
|
// Strangely, this doesn't seem to work if the text is longer than just one row
|
||||||
|
var element = document.createElement('textarea');
|
||||||
|
element.value = value;
|
||||||
|
element.style.position = 'absolute';
|
||||||
|
element.style.width = '10px';
|
||||||
|
element.style.height = '10px';
|
||||||
|
element.style.top = '-1000px';
|
||||||
|
element.style.left = '-1000px';
|
||||||
|
|
||||||
|
document.body.appendChild(element);
|
||||||
|
|
||||||
|
element.select();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('copy');
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.removeChild(element);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
@ -165,6 +165,7 @@ const topics = {
|
|||||||
|
|
||||||
function getTopicURL(topic, locale) {
|
function getTopicURL(topic, locale) {
|
||||||
if (!(topic in topics)) {
|
if (!(topic in topics)) {
|
||||||
|
console.warn(`help topic "${topic}" not found`);
|
||||||
// If topic doesn't exist, return default URL
|
// If topic doesn't exist, return default URL
|
||||||
return 'https://docs.datarhei.com/restreamer';
|
return 'https://docs.datarhei.com/restreamer';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -588,37 +588,30 @@ class Restreamer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (let device of val.devices.demuxers) {
|
for (let device of val.devices.demuxers) {
|
||||||
if (['avfoundation', 'video4linux2', 'alsa', 'fbdev'].includes(device.id)) {
|
if (!['avfoundation', 'video4linux2', 'alsa', 'fbdev'].includes(device.id)) {
|
||||||
if (device.devices.length === 0) {
|
continue;
|
||||||
continue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
skills.sources[device.id] = [];
|
// It's OK to have an empty list of devices because a device might get
|
||||||
|
// plugged meanwhile and a refresh is required.
|
||||||
|
skills.sources[device.id] = [];
|
||||||
|
|
||||||
// Split out a Raspberry Pi camera and create a dedicated source
|
// Split out a Raspberry Pi camera and create a dedicated source
|
||||||
if (device.id === 'video4linux2') {
|
if (device.id === 'video4linux2') {
|
||||||
for (let d of device.devices) {
|
for (let d of device.devices) {
|
||||||
if (d.extra.indexOf('bcm2835-v4l2') !== -1) {
|
if (d.extra.indexOf('bcm2835-v4l2') !== -1) {
|
||||||
if (!('raspicam' in skills.sources)) {
|
if (!('raspicam' in skills.sources)) {
|
||||||
skills.sources['raspicam'] = [];
|
skills.sources['raspicam'] = [];
|
||||||
}
|
|
||||||
skills.sources['raspicam'].push({ ...d });
|
|
||||||
} else {
|
|
||||||
skills.sources[device.id].push({ ...d });
|
|
||||||
}
|
}
|
||||||
}
|
skills.sources['raspicam'].push({ ...d });
|
||||||
} else {
|
} else {
|
||||||
for (let d of device.devices) {
|
|
||||||
skills.sources[device.id].push({ ...d });
|
skills.sources[device.id].push({ ...d });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
}
|
for (let d of device.devices) {
|
||||||
|
skills.sources[device.id].push({ ...d });
|
||||||
// Check if the V4L sources are empty and remove them in this case
|
}
|
||||||
if ('video4linux2' in skills.sources) {
|
|
||||||
if (skills.sources['video4linux2'].length === 0) {
|
|
||||||
delete skills.sources['video4linux2'];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1032,7 +1025,7 @@ class Restreamer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async DeleteChannel(channelid) {
|
async DeleteChannel(channelid) {
|
||||||
const channel = this.channels.get(channelid);
|
const channel = this.GetChannel(channelid);
|
||||||
if (channel === null) {
|
if (channel === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1045,8 +1038,6 @@ class Restreamer {
|
|||||||
await this.DeleteEgress(channel.channelid, egressid);
|
await this.DeleteEgress(channel.channelid, egressid);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.CleanupIngest(channel.channelid);
|
|
||||||
|
|
||||||
this.channels.delete(channel.channelid);
|
this.channels.delete(channel.channelid);
|
||||||
|
|
||||||
if (this.channels.size === 0) {
|
if (this.channels.size === 0) {
|
||||||
@ -1425,7 +1416,7 @@ class Restreamer {
|
|||||||
return await this._deleteProcess(channel.id);
|
return await this._deleteProcess(channel.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the inges snaphot process
|
// Delete the ingest snaphot process
|
||||||
async DeleteIngestSnapshot(channelid) {
|
async DeleteIngestSnapshot(channelid) {
|
||||||
const channel = this.GetChannel(channelid);
|
const channel = this.GetChannel(channelid);
|
||||||
if (channel === null) {
|
if (channel === null) {
|
||||||
@ -1473,11 +1464,13 @@ class Restreamer {
|
|||||||
{
|
{
|
||||||
pattern: `memfs:/${channel.channelid}_*.ts`,
|
pattern: `memfs:/${channel.channelid}_*.ts`,
|
||||||
max_files: parseInt(control.hls.listSize) + 2,
|
max_files: parseInt(control.hls.listSize) + 2,
|
||||||
max_file_age_seconds: parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2),
|
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2) : 0,
|
||||||
|
purge_on_delete: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
pattern: `memfs:/${channel.channelid}.m3u8`,
|
pattern: `memfs:/${channel.channelid}.m3u8`,
|
||||||
max_file_age_seconds: parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2),
|
max_file_age_seconds: control.hls.cleanup ? parseInt(control.hls.segmentDuration) * (parseInt(control.hls.listSize) + 2) : 0,
|
||||||
|
purge_on_delete: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@ -1565,12 +1558,18 @@ class Restreamer {
|
|||||||
id: 'output_0',
|
id: 'output_0',
|
||||||
address: `{memfs}/${channel.channelid}.jpg`,
|
address: `{memfs}/${channel.channelid}.jpg`,
|
||||||
options: ['-vframes', '1', '-f', 'image2', '-update', '1'],
|
options: ['-vframes', '1', '-f', 'image2', '-update', '1'],
|
||||||
|
cleanup: [
|
||||||
|
{
|
||||||
|
pattern: `memfs:/${channel.channelid}.jpg`,
|
||||||
|
purge_on_delete: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
options: ['-err_detect', 'ignore_err'],
|
options: ['-err_detect', 'ignore_err'],
|
||||||
autostart: control.process.autostart,
|
autostart: control.process.autostart,
|
||||||
reconnect: true,
|
reconnect: true,
|
||||||
reconnect_delay_seconds: control.snapshot.interval,
|
reconnect_delay_seconds: parseInt(control.snapshot.interval),
|
||||||
stale_timeout_seconds: 30,
|
stale_timeout_seconds: 30,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1592,30 +1591,6 @@ class Restreamer {
|
|||||||
return [val, null];
|
return [val, null];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup the ingest files from the memfs
|
|
||||||
async CleanupIngest(channelid) {
|
|
||||||
const channel = this.GetChannel(channelid);
|
|
||||||
if (channel === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let [files] = await this._call(this.api.MemFSListFiles, `/${channel.channelid}*`);
|
|
||||||
if (Array.isArray(files) === false) {
|
|
||||||
files = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
let cleanup = true;
|
|
||||||
|
|
||||||
for (let file of files) {
|
|
||||||
const [, err] = await this._call(this.api.MemFSDeleteFile, file.name);
|
|
||||||
if (err !== null) {
|
|
||||||
cleanup = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the manifest of the ingest process is available
|
// Check whether the manifest of the ingest process is available
|
||||||
async HasIngestFiles(channelid) {
|
async HasIngestFiles(channelid) {
|
||||||
const channel = this.GetChannel(channelid);
|
const channel = this.GetChannel(channelid);
|
||||||
@ -1870,11 +1845,12 @@ class Restreamer {
|
|||||||
player: 'videojs',
|
player: 'videojs',
|
||||||
playersite: true,
|
playersite: true,
|
||||||
channelid: 'current',
|
channelid: 'current',
|
||||||
titel: 'restreamer',
|
title: 'restreamer',
|
||||||
|
share: true,
|
||||||
support: true,
|
support: true,
|
||||||
template: '!default',
|
template: '!default',
|
||||||
templatename: '',
|
templatename: '',
|
||||||
textcolor_titel: 'rgba(255,255,255,1)',
|
textcolor_title: 'rgba(255,255,255,1)',
|
||||||
textcolor_default: 'rgba(230,230,230,1)',
|
textcolor_default: 'rgba(230,230,230,1)',
|
||||||
textcolor_link: 'rgba(230,230,230,1)',
|
textcolor_link: 'rgba(230,230,230,1)',
|
||||||
textcolor_link_hover: 'rgba(255,255,255,1)',
|
textcolor_link_hover: 'rgba(255,255,255,1)',
|
||||||
@ -1938,15 +1914,23 @@ class Restreamer {
|
|||||||
return arg1 === arg2 ? options.fn(this) : options.inverse(this);
|
return arg1 === arg2 ? options.fn(this) : options.inverse(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Handlebars.registerHelper('ifnoteq', function (arg1, arg2, options) {
|
||||||
|
if (arg1 !== arg2) {
|
||||||
|
return options.fn(this);
|
||||||
|
}
|
||||||
|
return options.inverse(this);
|
||||||
|
});
|
||||||
|
|
||||||
for (const item of channels) {
|
for (const item of channels) {
|
||||||
const ingestMetadata = await this.GetIngestMetadata(item.channelid);
|
const ingestMetadata = await this.GetIngestMetadata(item.channelid);
|
||||||
const templateData = {
|
const templateData = {
|
||||||
player: settings.player,
|
player: settings.player,
|
||||||
playersite: settings.playersite,
|
playersite: settings.playersite,
|
||||||
titel: settings.titel,
|
title: settings.title,
|
||||||
|
share: settings.share,
|
||||||
support: settings.support,
|
support: settings.support,
|
||||||
url: this.GetPlayersiteUrl(),
|
url: this.GetPlayersiteUrl(),
|
||||||
textcolor_titel: settings.textcolor_titel,
|
textcolor_title: settings.textcolor_title,
|
||||||
textcolor_default: settings.textcolor_default,
|
textcolor_default: settings.textcolor_default,
|
||||||
textcolor_link: settings.textcolor_link,
|
textcolor_link: settings.textcolor_link,
|
||||||
textcolor_link_hover: settings.textcolor_link_hover,
|
textcolor_link_hover: settings.textcolor_link_hover,
|
||||||
@ -2466,7 +2450,7 @@ class Restreamer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const findVersion = (name) => {
|
const findVersion = (name) => {
|
||||||
const matches = name.match(/\sv(\d+\.\d+\.\d+)\s?/);
|
const matches = name.match(/v(\d+\.\d+\.\d+)\s*$/);
|
||||||
if (matches === null) {
|
if (matches === null) {
|
||||||
return '0.0.0';
|
return '0.0.0';
|
||||||
}
|
}
|
||||||
@ -2477,10 +2461,12 @@ class Restreamer {
|
|||||||
const currentVersion = findVersion(Version.UI);
|
const currentVersion = findVersion(Version.UI);
|
||||||
const announcedVersion = findVersion(value.latest_version);
|
const announcedVersion = findVersion(value.latest_version);
|
||||||
|
|
||||||
if (SemverGt(announcedVersion, currentVersion)) {
|
if (currentVersion !== '0.0.0') {
|
||||||
this.hasUpdates = true;
|
if (SemverGt(announcedVersion, currentVersion)) {
|
||||||
} else {
|
this.hasUpdates = true;
|
||||||
this.hasUpdates = false;
|
} else {
|
||||||
|
this.hasUpdates = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const serviceVersion = findVersion(value.service_version);
|
const serviceVersion = findVersion(value.service_version);
|
||||||
@ -2625,14 +2611,15 @@ class Restreamer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (p.state) {
|
if (p.state) {
|
||||||
for (let i in p.state.progress.input) {
|
for (let i in p.state.progress.inputs) {
|
||||||
p.state.progress.input[i].address = replace(p.state.progress.input[i].address);
|
p.state.progress.inputs[i].address = replace(p.state.progress.inputs[i].address);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i in p.state.progress.output) {
|
for (let i in p.state.progress.outputs) {
|
||||||
p.state.progress.output[i].address = replace(p.state.progress.output[i].address);
|
p.state.progress.outputs[i].address = replace(p.state.progress.outputs[i].address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.state.command = p.state.command.map(replace);
|
||||||
p.state.last_logline = replace(p.state.last_logline);
|
p.state.last_logline = replace(p.state.last_logline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { name, version, bundle } from '../package.json';
|
import { name, version, bundle } from '../package.json';
|
||||||
|
|
||||||
const Core = '^15.0.0 || ^16.0.0';
|
const Core = '^15.0.0 || ^16.0.0';
|
||||||
const FFmpeg = '^4.1.0';
|
const FFmpeg = '^4.1.0 || ^5.0.0';
|
||||||
const UI = bundle ? bundle : name + ' v' + version;
|
const UI = bundle ? bundle : name + ' v' + version;
|
||||||
|
|
||||||
export { Core, FFmpeg, UI };
|
export { Core, FFmpeg, UI };
|
||||||
|
|||||||
@ -36,6 +36,7 @@ export default function Source(props) {
|
|||||||
modal: false,
|
modal: false,
|
||||||
status: 'none',
|
status: 'none',
|
||||||
});
|
});
|
||||||
|
const [$skillsRefresh, setSkillsRefresh] = React.useState(false);
|
||||||
const [$modal, setModal] = React.useState({
|
const [$modal, setModal] = React.useState({
|
||||||
open: false,
|
open: false,
|
||||||
data: '',
|
data: '',
|
||||||
@ -193,6 +194,12 @@ export default function Source(props) {
|
|||||||
return status === 'success';
|
return status === 'success';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
setSkillsRefresh(true);
|
||||||
|
await props.onRefresh();
|
||||||
|
setSkillsRefresh(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleEncoding = (type) => (encoder, decoder) => {
|
const handleEncoding = (type) => (encoder, decoder) => {
|
||||||
const profile = $profile[type];
|
const profile = $profile[type];
|
||||||
|
|
||||||
@ -321,6 +328,7 @@ export default function Source(props) {
|
|||||||
config={props.config}
|
config={props.config}
|
||||||
onProbe={handleProbe}
|
onProbe={handleProbe}
|
||||||
onChange={handleSourceSettingsChange}
|
onChange={handleSourceSettingsChange}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{$videoProbe.status !== 'none' && (
|
{$videoProbe.status !== 'none' && (
|
||||||
@ -426,6 +434,7 @@ export default function Source(props) {
|
|||||||
onProbe={handleProbe}
|
onProbe={handleProbe}
|
||||||
onSelect={handleSourceChange}
|
onSelect={handleSourceChange}
|
||||||
onChange={handleSourceSettingsChange}
|
onChange={handleSourceSettingsChange}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{$profile.custom.selected === false && $profile.custom.stream >= 0 && (
|
{$profile.custom.selected === false && $profile.custom.stream >= 0 && (
|
||||||
@ -544,7 +553,7 @@ export default function Source(props) {
|
|||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)}
|
)}
|
||||||
<Backdrop open={$videoProbe.probing || $audioProbe.probing}>
|
<Backdrop open={$videoProbe.probing || $audioProbe.probing || $skillsRefresh}>
|
||||||
<CircularProgress color="inherit" />
|
<CircularProgress color="inherit" />
|
||||||
</Backdrop>
|
</Backdrop>
|
||||||
<ProbeModal open={$modal.open} onClose={handleModal('none')} data={$modal.data} />
|
<ProbeModal open={$modal.open} onClose={handleModal('none')} data={$modal.data} />
|
||||||
@ -566,4 +575,5 @@ Source.defaultProps = {
|
|||||||
log: ['onProbe function not provided for this component'],
|
log: ['onProbe function not provided for this component'],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,22 +1,23 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { faMagic, faPen } from '@fortawesome/free-solid-svg-icons';
|
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans } from '@lingui/macro';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
|
import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
|
||||||
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
|
|
||||||
import * as M from '../../utils/metadata';
|
import * as M from '../../utils/metadata';
|
||||||
import Summary from './Summary';
|
import Summary from './Summary';
|
||||||
|
|
||||||
function WizardIcon(props) {
|
function IconWizard(props) {
|
||||||
return <FontAwesomeIcon icon={faMagic} {...props} />;
|
return (<AutoFixHighIcon {...props} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EditIcon(props) {
|
function IconEdit(props) {
|
||||||
return <FontAwesomeIcon icon={faPen} {...props} />;
|
return (<EditIcon {...props} />);
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
@ -56,10 +57,10 @@ export default function ProfileSummary(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton size="small" color="inherit" onClick={handleEdit('video')}>
|
<IconButton size="small" color="inherit" onClick={handleEdit('video')}>
|
||||||
<EditIcon />
|
<IconEdit />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton size="small" color="inherit" onClick={handleWizard()}>
|
<IconButton size="small" color="inherit" onClick={handleWizard()}>
|
||||||
<WizardIcon />
|
<IconWizard />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h3">
|
<Typography variant="h3">
|
||||||
<Trans>Video settings</Trans>
|
<Trans>Video settings</Trans>
|
||||||
@ -80,10 +81,10 @@ export default function ProfileSummary(props) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<IconButton size="small" color="inherit" onClick={handleEdit('audio')}>
|
<IconButton size="small" color="inherit" onClick={handleEdit('audio')}>
|
||||||
<EditIcon />
|
<IconEdit />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<IconButton size="small" color="inherit" onClick={handleWizard()}>
|
<IconButton size="small" color="inherit" onClick={handleWizard()}>
|
||||||
<WizardIcon />
|
<IconWizard />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h3" className={classes.title}>
|
<Typography variant="h3" className={classes.title}>
|
||||||
<Trans>Audio settings</Trans>
|
<Trans>Audio settings</Trans>
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import SemverSatisfies from 'semver/functions/satisfies';
|
||||||
|
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
|
||||||
import Sources from './Sources';
|
import Sources from './Sources';
|
||||||
|
|
||||||
function initConfig(initialConfig) {
|
function initConfig(initialConfig) {
|
||||||
@ -61,6 +64,10 @@ export default function SourceSelect(props) {
|
|||||||
props.onSelect(props.type, source);
|
props.onSelect(props.type, source);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
await props.onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
const handleProbe = async (settings, inputs) => {
|
const handleProbe = async (settings, inputs) => {
|
||||||
await props.onProbe(props.type, $source, settings, inputs);
|
await props.onProbe(props.type, $source, settings, inputs);
|
||||||
};
|
};
|
||||||
@ -80,22 +87,31 @@ export default function SourceSelect(props) {
|
|||||||
if (s !== null) {
|
if (s !== null) {
|
||||||
const Component = s.component;
|
const Component = s.component;
|
||||||
|
|
||||||
sourceControl = (
|
if (SemverSatisfies(props.skills.ffmpeg.version, s.ffversion)) {
|
||||||
<Component
|
sourceControl = (
|
||||||
knownDevices={props.skills.sources[$source]}
|
<Component
|
||||||
skills={props.skills}
|
knownDevices={props.skills.sources[$source]}
|
||||||
config={config[$source]}
|
skills={props.skills}
|
||||||
settings={$settings[$source]}
|
config={config[$source]}
|
||||||
onChange={handleChange($source)}
|
settings={$settings[$source]}
|
||||||
onProbe={handleProbe}
|
onChange={handleChange($source)}
|
||||||
/>
|
onProbe={handleProbe}
|
||||||
);
|
onRefresh={handleRefresh}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Select type={props.type} selected={$source} availableSources={props.skills.sources} onSelect={handleSource} />
|
<Select
|
||||||
|
type={props.type}
|
||||||
|
selected={$source}
|
||||||
|
ffversion={props.skills.ffmpeg.version}
|
||||||
|
availableSources={props.skills.sources}
|
||||||
|
onSelect={handleSource}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{sourceControl}
|
{sourceControl}
|
||||||
@ -112,6 +128,7 @@ SourceSelect.defaultProps = {
|
|||||||
onProbe: function (type, device, settings, inputs) {},
|
onProbe: function (type, device, settings, inputs) {},
|
||||||
onSelect: function (type, device) {},
|
onSelect: function (type, device) {},
|
||||||
onChange: function (type, device, settings) {},
|
onChange: function (type, device, settings) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function Select(props) {
|
function Select(props) {
|
||||||
@ -130,6 +147,10 @@ function Select(props) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!SemverSatisfies(props.ffversion, s.ffversion)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const variant = s.id === props.selected ? 'bigSelected' : 'big';
|
const variant = s.id === props.selected ? 'bigSelected' : 'big';
|
||||||
const Icon = s.icon;
|
const Icon = s.icon;
|
||||||
|
|
||||||
@ -145,6 +166,18 @@ function Select(props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (availableSources.length === 0) {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography variant="body1">
|
||||||
|
<Trans>No sources available</Trans>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
{availableSources}
|
{availableSources}
|
||||||
@ -155,6 +188,7 @@ function Select(props) {
|
|||||||
Select.defaultProps = {
|
Select.defaultProps = {
|
||||||
type: '',
|
type: '',
|
||||||
selected: '',
|
selected: '',
|
||||||
|
ffversion: '0.0.0',
|
||||||
availableSources: {},
|
availableSources: {},
|
||||||
onSelect: function (source) {},
|
onSelect: function (source) {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import React from 'react';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Icon from '@mui/icons-material/Usb';
|
import Icon from '@mui/icons-material/Usb';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
@ -73,6 +75,10 @@ function Source(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
props.onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
const handleProbe = () => {
|
const handleProbe = () => {
|
||||||
props.onProbe(settings, createInputs(settings));
|
props.onProbe(settings, createInputs(settings));
|
||||||
};
|
};
|
||||||
@ -95,18 +101,6 @@ function Source(props) {
|
|||||||
label: i18n._(t`Custom ...`),
|
label: i18n._(t`Custom ...`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const audioDevices = (
|
|
||||||
<SelectCustom
|
|
||||||
options={options}
|
|
||||||
label={<Trans>Audio device</Trans>}
|
|
||||||
customLabel={<Trans>Custom audio device</Trans>}
|
|
||||||
value={settings.device}
|
|
||||||
onChange={handleChange('device')}
|
|
||||||
variant="outlined"
|
|
||||||
allowCustom
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="flex-start" spacing={2} className={classes.gridContainer}>
|
<Grid container alignItems="flex-start" spacing={2} className={classes.gridContainer}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@ -115,7 +109,18 @@ function Source(props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{audioDevices}
|
<SelectCustom
|
||||||
|
options={options}
|
||||||
|
label={<Trans>Audio device</Trans>}
|
||||||
|
customLabel={<Trans>Custom audio device</Trans>}
|
||||||
|
value={settings.device}
|
||||||
|
onChange={handleChange('device')}
|
||||||
|
variant="outlined"
|
||||||
|
allowCustom
|
||||||
|
/>
|
||||||
|
<Button size="small" startIcon={<RefreshIcon />} onClick={handleRefresh} sx={{ float: 'right' }}>
|
||||||
|
<Trans>Refresh</Trans>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Audio.Sampling value={settings.sampling} onChange={handleChange('sampling')} allowCustom />
|
<Audio.Sampling value={settings.sampling} onChange={handleChange('sampling')} allowCustom />
|
||||||
@ -140,6 +145,7 @@ Source.defaultProps = {
|
|||||||
settings: {},
|
settings: {},
|
||||||
onChange: function (settings) {},
|
onChange: function (settings) {},
|
||||||
onProbe: function (settings, inputs) {},
|
onProbe: function (settings, inputs) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function SourceIcon(props) {
|
function SourceIcon(props) {
|
||||||
@ -149,10 +155,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'alsa';
|
const id = 'alsa';
|
||||||
const name = <Trans>ALSA</Trans>;
|
const name = <Trans>ALSA</Trans>;
|
||||||
const capabilities = ['audio'];
|
const capabilities = ['audio'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -3,8 +3,10 @@ import React from 'react';
|
|||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Icon from '@mui/icons-material/Apple';
|
import Icon from '@mui/icons-material/Apple';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import Checkbox from '../../../misc/Checkbox';
|
import Checkbox from '../../../misc/Checkbox';
|
||||||
@ -92,6 +94,10 @@ function Source(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
props.onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
const handleProbe = () => {
|
const handleProbe = () => {
|
||||||
props.onProbe(settings, createInputs(settings));
|
props.onProbe(settings, createInputs(settings));
|
||||||
};
|
};
|
||||||
@ -171,6 +177,9 @@ function Source(props) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{audioDevices}
|
{audioDevices}
|
||||||
|
<Button size="small" startIcon={<RefreshIcon />} onClick={handleRefresh} sx={{ float: 'right' }}>
|
||||||
|
<Trans>Refresh</Trans>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Video.Format value={settings.format} onChange={handleChange('format')} allowCustom />
|
<Video.Format value={settings.format} onChange={handleChange('format')} allowCustom />
|
||||||
@ -201,6 +210,7 @@ Source.defaultProps = {
|
|||||||
settings: {},
|
settings: {},
|
||||||
onChange: function (settings) {},
|
onChange: function (settings) {},
|
||||||
onProbe: function (settings, inputs) {},
|
onProbe: function (settings, inputs) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function SourceIcon(props) {
|
function SourceIcon(props) {
|
||||||
@ -210,10 +220,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'avfoundation';
|
const id = 'avfoundation';
|
||||||
const name = <Trans>AVFoundation</Trans>;
|
const name = <Trans>AVFoundation</Trans>;
|
||||||
const capabilities = ['audio', 'video'];
|
const capabilities = ['audio', 'video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -128,10 +128,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'fbdev';
|
const id = 'fbdev';
|
||||||
const name = <Trans>Framebuffer</Trans>;
|
const name = <Trans>Framebuffer</Trans>;
|
||||||
const capabilities = ['video'];
|
const capabilities = ['video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import SemverSatisfies from 'semver/functions/satisfies';
|
||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
@ -116,10 +117,16 @@ const initSkills = (initialSkills) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const skills = {
|
const skills = {
|
||||||
|
ffmpeg: {},
|
||||||
protocols: {},
|
protocols: {},
|
||||||
...initialSkills,
|
...initialSkills,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
skills.ffmpeg = {
|
||||||
|
version: '0.0.0',
|
||||||
|
...skills.version,
|
||||||
|
};
|
||||||
|
|
||||||
skills.protocols = {
|
skills.protocols = {
|
||||||
input: [],
|
input: [],
|
||||||
...skills.protocols,
|
...skills.protocols,
|
||||||
@ -132,6 +139,11 @@ const createInputs = (settings, config, skills) => {
|
|||||||
config = initConfig(config);
|
config = initConfig(config);
|
||||||
skills = initSkills(skills);
|
skills = initSkills(skills);
|
||||||
|
|
||||||
|
let ffmpeg_version = 4;
|
||||||
|
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||||
|
ffmpeg_version = 5;
|
||||||
|
}
|
||||||
|
|
||||||
const input = {
|
const input = {
|
||||||
address: '',
|
address: '',
|
||||||
options: [],
|
options: [],
|
||||||
@ -159,7 +171,11 @@ const createInputs = (settings, config, skills) => {
|
|||||||
input.address = addUsernamePassword(input.address, settings.username, settings.password);
|
input.address = addUsernamePassword(input.address, settings.username, settings.password);
|
||||||
|
|
||||||
if (protocol === 'rtsp') {
|
if (protocol === 'rtsp') {
|
||||||
input.options.push('-stimeout', settings.rtsp.stimeout);
|
if (ffmpeg_version === 4) {
|
||||||
|
input.options.push('-stimeout', settings.rtsp.stimeout);
|
||||||
|
} else {
|
||||||
|
input.options.push('-timeout', settings.rtsp.stimeout);
|
||||||
|
}
|
||||||
|
|
||||||
if (settings.rtsp.udp === true) {
|
if (settings.rtsp.udp === true) {
|
||||||
input.options.push('-rtsp_transport', 'udp');
|
input.options.push('-rtsp_transport', 'udp');
|
||||||
@ -209,10 +225,10 @@ const addUsernamePassword = (address, username, password) => {
|
|||||||
|
|
||||||
const url = urlparser(address, {});
|
const url = urlparser(address, {});
|
||||||
if (username.length !== 0) {
|
if (username.length !== 0) {
|
||||||
url.set('username', encodeURIComponent(username));
|
url.set('username', username);
|
||||||
}
|
}
|
||||||
if (password.length !== 0) {
|
if (password.length !== 0) {
|
||||||
url.set('password', encodeURIComponent(password));
|
url.set('password', password);
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
@ -667,6 +683,7 @@ function SourceIcon(props) {
|
|||||||
const id = 'network';
|
const id = 'network';
|
||||||
const name = <Trans>Network source</Trans>;
|
const name = <Trans>Network source</Trans>;
|
||||||
const capabilities = ['audio', 'video'];
|
const capabilities = ['audio', 'video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
@ -680,4 +697,4 @@ const func = {
|
|||||||
isAuthProtocol,
|
isAuthProtocol,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -44,10 +44,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'noaudio';
|
const id = 'noaudio';
|
||||||
const name = <Trans>No audio</Trans>;
|
const name = <Trans>No audio</Trans>;
|
||||||
const capabilities = ['audio'];
|
const capabilities = ['audio'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -131,10 +131,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'raspicam';
|
const id = 'raspicam';
|
||||||
const name = <Trans>Raspberry Pi camera</Trans>;
|
const name = <Trans>Raspberry Pi camera</Trans>;
|
||||||
const capabilities = ['video'];
|
const capabilities = ['video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import { useLingui } from '@lingui/react';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
import makeStyles from '@mui/styles/makeStyles';
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import FormInlineButton from '../../../misc/FormInlineButton';
|
import FormInlineButton from '../../../misc/FormInlineButton';
|
||||||
@ -67,6 +69,10 @@ function Source(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
props.onRefresh();
|
||||||
|
};
|
||||||
|
|
||||||
const handleProbe = () => {
|
const handleProbe = () => {
|
||||||
props.onProbe(settings, createInputs(settings));
|
props.onProbe(settings, createInputs(settings));
|
||||||
};
|
};
|
||||||
@ -90,18 +96,6 @@ function Source(props) {
|
|||||||
label: i18n._(t`Custom ...`),
|
label: i18n._(t`Custom ...`),
|
||||||
});
|
});
|
||||||
|
|
||||||
const videoDevices = (
|
|
||||||
<SelectCustom
|
|
||||||
options={options}
|
|
||||||
label={<Trans>Video device</Trans>}
|
|
||||||
customLabel={<Trans>Custom video device</Trans>}
|
|
||||||
value={settings.device}
|
|
||||||
onChange={handleChange('device')}
|
|
||||||
variant="outlined"
|
|
||||||
allowCustom
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container alignItems="flex-start" spacing={2} className={classes.gridContainer}>
|
<Grid container alignItems="flex-start" spacing={2} className={classes.gridContainer}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@ -110,7 +104,18 @@ function Source(props) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{videoDevices}
|
<SelectCustom
|
||||||
|
options={options}
|
||||||
|
label={<Trans>Video device</Trans>}
|
||||||
|
customLabel={<Trans>Custom video device</Trans>}
|
||||||
|
value={settings.device}
|
||||||
|
onChange={handleChange('device')}
|
||||||
|
variant="outlined"
|
||||||
|
allowCustom
|
||||||
|
/>
|
||||||
|
<Button size="small" startIcon={<RefreshIcon />} onClick={handleRefresh} sx={{ float: 'right' }}>
|
||||||
|
<Trans>Refresh</Trans>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Video.Format value={settings.format} onChange={handleChange('format')} allowCustom />
|
<Video.Format value={settings.format} onChange={handleChange('format')} allowCustom />
|
||||||
@ -135,6 +140,7 @@ Source.defaultProps = {
|
|||||||
settings: {},
|
settings: {},
|
||||||
onChange: function (settings) {},
|
onChange: function (settings) {},
|
||||||
onProbe: function (settings, inputs) {},
|
onProbe: function (settings, inputs) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function SourceIcon(props) {
|
function SourceIcon(props) {
|
||||||
@ -144,10 +150,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'video4linux2';
|
const id = 'video4linux2';
|
||||||
const name = <Trans>Hardware device</Trans>;
|
const name = <Trans>Hardware device</Trans>;
|
||||||
const capabilities = ['video'];
|
const capabilities = ['video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -44,10 +44,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'videoaudio';
|
const id = 'videoaudio';
|
||||||
const name = <Trans>Video source</Trans>;
|
const name = <Trans>Video source</Trans>;
|
||||||
const capabilities = ['audio'];
|
const capabilities = ['audio'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -81,7 +81,7 @@ function Source(props) {
|
|||||||
<Grid container alignItems="flex-start" spacing={2} style={{ marginTop: '0.5em' }}>
|
<Grid container alignItems="flex-start" spacing={2} style={{ marginTop: '0.5em' }}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<Trans>Select audio Source:</Trans>
|
<Trans>Select audio source:</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@ -186,10 +186,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'virtualaudio';
|
const id = 'virtualaudio';
|
||||||
const name = <Trans>Virtual source</Trans>;
|
const name = <Trans>Virtual source</Trans>;
|
||||||
const capabilities = ['audio'];
|
const capabilities = ['audio'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -161,10 +161,22 @@ function Source(props) {
|
|||||||
<TextField variant="outlined" fullWidth label={<Trans>Ratio</Trans>} value={settings.ratio} onChange={handleChange('ratio')} />
|
<TextField variant="outlined" fullWidth label={<Trans>Ratio</Trans>} value={settings.ratio} onChange={handleChange('ratio')} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField variant="outlined" fullWidth label={<Trans>Death color</Trans>} value={settings.death_color} onChange={handleChange('death_color')} />
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
label={<Trans>Death color</Trans>}
|
||||||
|
value={settings.death_color}
|
||||||
|
onChange={handleChange('death_color')}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField variant="outlined" fullWidth label={<Trans>Life color</Trans>} value={settings.life_color} onChange={handleChange('life_color')} />
|
<TextField
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
label={<Trans>Life color</Trans>}
|
||||||
|
value={settings.life_color}
|
||||||
|
onChange={handleChange('life_color')}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<TextField variant="outlined" fullWidth label={<Trans>Flags</Trans>} value={settings.flags} onChange={handleChange('flags')} />
|
<TextField variant="outlined" fullWidth label={<Trans>Flags</Trans>} value={settings.flags} onChange={handleChange('flags')} />
|
||||||
@ -193,10 +205,11 @@ function SourceIcon(props) {
|
|||||||
const id = 'virtualvideo';
|
const id = 'virtualvideo';
|
||||||
const name = <Trans>Virtual source</Trans>;
|
const name = <Trans>Virtual source</Trans>;
|
||||||
const capabilities = ['video'];
|
const capabilities = ['video'];
|
||||||
|
const ffversion = '^4.1.0 || ^5.0.0';
|
||||||
|
|
||||||
const func = {
|
const func = {
|
||||||
initSettings,
|
initSettings,
|
||||||
createInputs,
|
createInputs,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { id, name, capabilities, SourceIcon as icon, Source as component, func };
|
export { id, name, capabilities, ffversion, SourceIcon as icon, Source as component, func };
|
||||||
|
|||||||
@ -2,9 +2,11 @@ import React from 'react';
|
|||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Icon from '@mui/icons-material/Apple';
|
import Icon from '@mui/icons-material/Apple';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import * as S from '../../Sources/AVFoundation';
|
import * as S from '../../Sources/AVFoundation';
|
||||||
@ -28,7 +30,13 @@ function Source(props) {
|
|||||||
const handleChange = (newSettings) => {
|
const handleChange = (newSettings) => {
|
||||||
newSettings = newSettings || settings;
|
newSettings = newSettings || settings;
|
||||||
|
|
||||||
props.onChange(S.id, newSettings, S.func.createInputs(newSettings), true);
|
const filteredDevices = props.knownDevices.filter((device) => device.media === 'video');
|
||||||
|
|
||||||
|
props.onChange(S.id, newSettings, S.func.createInputs(newSettings), filteredDevices.length !== 0 ? true : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
props.onRefresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (what) => (event) => {
|
const update = (what) => (event) => {
|
||||||
@ -56,11 +64,19 @@ function Source(props) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
options.unshift(
|
if (options.length === 0) {
|
||||||
<MenuItem key="default" value="default">
|
options.push(
|
||||||
{i18n._(t`Default`)}
|
<MenuItem key="none" value="none" disabled={true}>
|
||||||
</MenuItem>
|
{i18n._(t`No input device available`)}
|
||||||
);
|
</MenuItem>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
options.unshift(
|
||||||
|
<MenuItem key="default" value="default">
|
||||||
|
{i18n._(t`Default`)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const videoDevices = (
|
const videoDevices = (
|
||||||
<Select label={<Trans>Video device</Trans>} value={settings.vindex} onChange={update('vindex')}>
|
<Select label={<Trans>Video device</Trans>} value={settings.vindex} onChange={update('vindex')}>
|
||||||
@ -82,11 +98,14 @@ function Source(props) {
|
|||||||
{i18n._(t`None`)}
|
{i18n._(t`None`)}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
options.unshift(
|
|
||||||
<MenuItem key="default" value="default">
|
if (options.length > 1) {
|
||||||
{i18n._(t`Default`)}
|
options.unshift(
|
||||||
</MenuItem>
|
<MenuItem key="default" value="default">
|
||||||
);
|
{i18n._(t`Default`)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const audioDevices = (
|
const audioDevices = (
|
||||||
<Select label={<Trans>Audio Device</Trans>} value={settings.aindex} onChange={update('aindex')}>
|
<Select label={<Trans>Audio Device</Trans>} value={settings.aindex} onChange={update('aindex')}>
|
||||||
@ -112,6 +131,9 @@ function Source(props) {
|
|||||||
<Grid container alignItems="center" spacing={1}>
|
<Grid container alignItems="center" spacing={1}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{audioDevices}
|
{audioDevices}
|
||||||
|
<Button size="small" startIcon={<RefreshIcon />} onClick={handleRefresh} sx={{ float: 'right' }}>
|
||||||
|
<Trans>Refresh</Trans>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -123,6 +145,7 @@ Source.defaultProps = {
|
|||||||
knownDevices: [],
|
knownDevices: [],
|
||||||
settings: {},
|
settings: {},
|
||||||
onChange: function (type, settings, inputs, ready) {},
|
onChange: function (type, settings, inputs, ready) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function SourceIcon(props) {
|
function SourceIcon(props) {
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
|
import { useLingui } from '@lingui/react';
|
||||||
import { faUsb } from '@fortawesome/free-brands-svg-icons';
|
import { faUsb } from '@fortawesome/free-brands-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Trans } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
import * as S from '../../Sources/V4L';
|
import * as S from '../../Sources/V4L';
|
||||||
@ -52,13 +55,18 @@ function initDevices(initialDevices) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Source(props) {
|
function Source(props) {
|
||||||
|
const { i18n } = useLingui();
|
||||||
const settings = initSettings(props.settings, props.knownDevices);
|
const settings = initSettings(props.settings, props.knownDevices);
|
||||||
const devices = initDevices(props.knownDevices);
|
const devices = initDevices(props.knownDevices);
|
||||||
|
|
||||||
const handleChange = (newSettings) => {
|
const handleChange = (newSettings) => {
|
||||||
newSettings = newSettings || settings;
|
newSettings = newSettings || settings;
|
||||||
|
|
||||||
props.onChange(S.id, newSettings, S.func.createInputs(newSettings), true);
|
props.onChange(S.id, newSettings, S.func.createInputs(newSettings), devices.length !== 0 ? true : false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
props.onRefresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
const update = (what) => (event) => {
|
const update = (what) => (event) => {
|
||||||
@ -85,6 +93,14 @@ function Source(props) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (options.length === 0) {
|
||||||
|
options.push(
|
||||||
|
<MenuItem key="none" value="none" disabled={true}>
|
||||||
|
{i18n._(t`No input device available`)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const videoDevices = (
|
const videoDevices = (
|
||||||
<Select label={<Trans>Video device</Trans>} value={settings.device} onChange={update('device')}>
|
<Select label={<Trans>Video device</Trans>} value={settings.device} onChange={update('device')}>
|
||||||
{options}
|
{options}
|
||||||
@ -100,6 +116,9 @@ function Source(props) {
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{videoDevices}
|
{videoDevices}
|
||||||
|
<Button size="small" startIcon={<RefreshIcon />} onClick={handleRefresh} sx={{ float: 'right' }}>
|
||||||
|
<Trans>Refresh</Trans>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@ -109,6 +128,7 @@ Source.defaultProps = {
|
|||||||
knownDevices: [],
|
knownDevices: [],
|
||||||
settings: {},
|
settings: {},
|
||||||
onChange: function (type, settings, inputs, ready) {},
|
onChange: function (type, settings, inputs, ready) {},
|
||||||
|
onRefresh: function () {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function SourceIcon(props) {
|
function SourceIcon(props) {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
|
|
||||||
import { useLingui } from '@lingui/react';
|
import { useLingui } from '@lingui/react';
|
||||||
import { Trans, t } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
|
import Backdrop from '@mui/material/Backdrop';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
@ -48,6 +49,7 @@ export default function Wizard(props) {
|
|||||||
probing: false,
|
probing: false,
|
||||||
status: 'none',
|
status: 'none',
|
||||||
});
|
});
|
||||||
|
const [$skillsRefresh, setSkillsRefresh] = React.useState(false);
|
||||||
const [$abort, setAbort] = React.useState({
|
const [$abort, setAbort] = React.useState({
|
||||||
step: 'TYPE',
|
step: 'TYPE',
|
||||||
});
|
});
|
||||||
@ -83,6 +85,13 @@ export default function Wizard(props) {
|
|||||||
setReady(true);
|
setReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const refreshSkills = async () => {
|
||||||
|
await props.restreamer.RefreshSkills();
|
||||||
|
|
||||||
|
const skills = await props.restreamer.Skills();
|
||||||
|
setSkills(skills);
|
||||||
|
};
|
||||||
|
|
||||||
const probe = async (type, source) => {
|
const probe = async (type, source) => {
|
||||||
setProbe({
|
setProbe({
|
||||||
...$probe,
|
...$probe,
|
||||||
@ -164,8 +173,6 @@ export default function Wizard(props) {
|
|||||||
|
|
||||||
data.streams = M.createOutputStreams(sources, profiles);
|
data.streams = M.createOutputStreams(sources, profiles);
|
||||||
|
|
||||||
await props.restreamer.CleanupIngest(_channelid);
|
|
||||||
|
|
||||||
const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control);
|
const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control);
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
notify.Dispatch('error', 'save:ingest', err.message);
|
notify.Dispatch('error', 'save:ingest', err.message);
|
||||||
@ -327,6 +334,12 @@ export default function Wizard(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = async () => {
|
||||||
|
setSkillsRefresh(true);
|
||||||
|
await refreshSkills();
|
||||||
|
setSkillsRefresh(false);
|
||||||
|
};
|
||||||
|
|
||||||
const s = Sources.Get($sourceid);
|
const s = Sources.Get($sourceid);
|
||||||
if (s === null) {
|
if (s === null) {
|
||||||
setStep('TYPE');
|
setStep('TYPE');
|
||||||
@ -349,6 +362,7 @@ export default function Wizard(props) {
|
|||||||
settings={$sources.video.settings}
|
settings={$sources.video.settings}
|
||||||
skills={$skills}
|
skills={$skills}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
/>
|
/>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
{$probe.status === 'error' && (
|
{$probe.status === 'error' && (
|
||||||
@ -400,6 +414,9 @@ export default function Wizard(props) {
|
|||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Backdrop open={$skillsRefresh}>
|
||||||
|
<CircularProgress color="inherit" />
|
||||||
|
</Backdrop>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1049,8 +1066,8 @@ export default function Wizard(props) {
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography>
|
<Typography>
|
||||||
<Trans>
|
<Trans>
|
||||||
Use your copyright and choose the correct image license. Whether free for all or highly restricted.
|
Use your copyright and choose the correct image license. Whether free for all or highly restricted. Briefly discuss what others
|
||||||
Briefly discuss what others are allowed to do with your image.
|
are allowed to do with your image.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -195,6 +195,13 @@ export default function Edit(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSkillsRefresh = async () => {
|
||||||
|
await props.restreamer.RefreshSkills();
|
||||||
|
|
||||||
|
const skills = await props.restreamer.Skills();
|
||||||
|
setSkills(skills);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSourceProbe = async (inputs) => {
|
const handleSourceProbe = async (inputs) => {
|
||||||
let [res, err] = await props.restreamer.Probe(_channelid, inputs);
|
let [res, err] = await props.restreamer.Probe(_channelid, inputs);
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
@ -284,12 +291,6 @@ export default function Edit(props) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup previous files
|
|
||||||
let res = await props.restreamer.CleanupIngest(_channelid);
|
|
||||||
if (res === false) {
|
|
||||||
notify.Dispatch('warning', 'save:ingest', i18n._(t`Failed to correctly cleanup previous process data`));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/update the ingest
|
// Create/update the ingest
|
||||||
const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control);
|
const [, err] = await props.restreamer.UpsertIngest(_channelid, inputs, outputs, control);
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
@ -298,7 +299,7 @@ export default function Edit(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save the metadata
|
// Save the metadata
|
||||||
res = await props.restreamer.SetIngestMetadata(_channelid, $data);
|
let res = await props.restreamer.SetIngestMetadata(_channelid, $data);
|
||||||
if (res === false) {
|
if (res === false) {
|
||||||
notify.Dispatch('warning', 'save:ingest', i18n._(t`Failed to save ingest metadata`));
|
notify.Dispatch('warning', 'save:ingest', i18n._(t`Failed to save ingest metadata`));
|
||||||
}
|
}
|
||||||
@ -449,6 +450,7 @@ export default function Edit(props) {
|
|||||||
config={$config.source}
|
config={$config.source}
|
||||||
startWith={$state.edit}
|
startWith={$state.edit}
|
||||||
onProbe={handleSourceProbe}
|
onProbe={handleSourceProbe}
|
||||||
|
onRefresh={handleSkillsRefresh}
|
||||||
onDone={handleSourceDone}
|
onDone={handleSourceDone}
|
||||||
onAbort={handleSourceAbort}
|
onAbort={handleSourceAbort}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -226,7 +226,7 @@ export default function Login(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper xs={11} sm={10} md={6}>
|
<Paper xs={11} sm={10} md={6}>
|
||||||
<Grid container spacing={3} padding={{xs: 1, sm: 5}}>
|
<Grid container spacing={3} padding={{ xs: 1, sm: 5 }}>
|
||||||
{$loginTarget !== 'none' && (
|
{$loginTarget !== 'none' && (
|
||||||
<Grid item xs={12} align="center">
|
<Grid item xs={12} align="center">
|
||||||
{$auths.length > 1 && (
|
{$auths.length > 1 && (
|
||||||
|
|||||||
@ -6,11 +6,13 @@ import makeStyles from '@mui/styles/makeStyles';
|
|||||||
import CircularProgress from '@mui/material/CircularProgress';
|
import CircularProgress from '@mui/material/CircularProgress';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
|
|
||||||
import useInterval from '../../hooks/useInterval';
|
import useInterval from '../../hooks/useInterval';
|
||||||
import ActionButton from '../../misc/ActionButton';
|
import ActionButton from '../../misc/ActionButton';
|
||||||
|
import CopyButton from '../../misc/CopyButton';
|
||||||
import DebugModal from '../../misc/modals/Debug';
|
import DebugModal from '../../misc/modals/Debug';
|
||||||
import H from '../../utils/help';
|
import H from '../../utils/help';
|
||||||
import Paper from '../../misc/Paper';
|
import Paper from '../../misc/Paper';
|
||||||
@ -19,7 +21,6 @@ import Player from '../../misc/Player';
|
|||||||
import Progress from './Progress';
|
import Progress from './Progress';
|
||||||
import Publication from './Publication';
|
import Publication from './Publication';
|
||||||
import ProcessModal from '../../misc/modals/Process';
|
import ProcessModal from '../../misc/modals/Process';
|
||||||
import TextFieldCopy from '../../misc/TextFieldCopy';
|
|
||||||
import Welcome from '../Welcome';
|
import Welcome from '../Welcome';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
@ -141,7 +142,6 @@ export default function Main(props) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await props.restreamer.CleanupIngest(_channelid);
|
|
||||||
await props.restreamer.StartIngest(_channelid);
|
await props.restreamer.StartIngest(_channelid);
|
||||||
await props.restreamer.StartIngestSnapshot(_channelid);
|
await props.restreamer.StartIngestSnapshot(_channelid);
|
||||||
};
|
};
|
||||||
@ -156,9 +156,6 @@ export default function Main(props) {
|
|||||||
await props.restreamer.StopIngest(_channelid);
|
await props.restreamer.StopIngest(_channelid);
|
||||||
|
|
||||||
await disconnectEgresses();
|
await disconnectEgresses();
|
||||||
|
|
||||||
// Cleanup previous files
|
|
||||||
await props.restreamer.CleanupIngest(_channelid);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const reconnect = async () => {
|
const reconnect = async () => {
|
||||||
@ -363,10 +360,19 @@ export default function Main(props) {
|
|||||||
<Progress progress={$state.progress} />
|
<Progress progress={$state.progress} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} marginTop="-.2em">
|
<Grid item xs={12} marginTop="-.2em">
|
||||||
<TextFieldCopy
|
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
||||||
label={<Trans>HLS URL</Trans>}
|
<Typography variant="body">
|
||||||
value={address + manifest}
|
<Trans>Content URL</Trans>
|
||||||
/>
|
</Typography>
|
||||||
|
<Stack direction="row" justifyContent="flex-end" alignItems="center" spacing={0.5}>
|
||||||
|
<CopyButton variant="outlined" color="default" size="small" value={address + manifest}>
|
||||||
|
<Trans>HLS</Trans>
|
||||||
|
</CopyButton>
|
||||||
|
<CopyButton variant="outlined" color="default" size="small" value={address + poster}>
|
||||||
|
<Trans>Snapshot</Trans>
|
||||||
|
</CopyButton>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} marginTop="0em">
|
<Grid item xs={12} marginTop="0em">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export default function ResetPassword(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const invalid = $login.username.length === 0 || $login.password.length === 0 || ($login.password !== $login.passwordConfirm);
|
const invalid = $login.username.length === 0 || $login.password.length === 0 || $login.password !== $login.passwordConfirm;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper xs={12} sm={10} md={6} className="PaperL">
|
<Paper xs={12} sm={10} md={6} className="PaperL">
|
||||||
|
|||||||
@ -98,7 +98,7 @@ export default function Playersite(props) {
|
|||||||
const value = event.target.value;
|
const value = event.target.value;
|
||||||
const settings = $settings;
|
const settings = $settings;
|
||||||
|
|
||||||
if (['playersite', 'header', 'support'].includes(what)) {
|
if (['playersite', 'header', 'share', 'support'].includes(what)) {
|
||||||
settings[what] = !settings[what];
|
settings[what] = !settings[what];
|
||||||
} else {
|
} else {
|
||||||
settings[what] = value;
|
settings[what] = value;
|
||||||
@ -136,9 +136,10 @@ export default function Playersite(props) {
|
|||||||
if (type === null) {
|
if (type === null) {
|
||||||
// not one of the allowed mimetypes
|
// not one of the allowed mimetypes
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
const types = imageAcceptString;
|
||||||
showUploadError(
|
showUploadError(
|
||||||
<Trans>
|
<Trans>
|
||||||
The selected file type ({file.type}) is now allowed. Allowed file types are {imageAcceptString}
|
The selected file type ({file.type}) is not allowed. Allowed file types are {types}
|
||||||
</Trans>
|
</Trans>
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -210,9 +211,10 @@ export default function Playersite(props) {
|
|||||||
if (type === null) {
|
if (type === null) {
|
||||||
// not one of the allowed mimetypes
|
// not one of the allowed mimetypes
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
const types = templateAcceptString;
|
||||||
showUploadError(
|
showUploadError(
|
||||||
<Trans>
|
<Trans>
|
||||||
The selected file type ({file.type}) is now allowed. Allowed file types are {templateAcceptString}
|
The selected file type ({file.type}) is not allowed. Allowed file types are {types}
|
||||||
</Trans>
|
</Trans>
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@ -235,7 +237,7 @@ export default function Playersite(props) {
|
|||||||
if (reader.result === null) {
|
if (reader.result === null) {
|
||||||
// reading the file failed
|
// reading the file failed
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
showUploadError(<Trans>There war an error during upload: {reader.error.message}</Trans>);
|
showUploadError(<Trans>There was an error during upload: {reader.error.message}</Trans>);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,7 +366,8 @@ export default function Playersite(props) {
|
|||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
<Trans>
|
<Trans>
|
||||||
In addition to the player, the Restreamer offers a complete landingpage, which you can use to present your live stream easily and quickly.
|
In addition to the player, the Restreamer offers a complete landingpage, which you can use to present your live
|
||||||
|
stream easily and quickly.
|
||||||
</Trans>
|
</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -377,8 +380,8 @@ export default function Playersite(props) {
|
|||||||
fullWidth
|
fullWidth
|
||||||
label={<Trans>Sitename</Trans>}
|
label={<Trans>Sitename</Trans>}
|
||||||
disabled={!$settings.playersite}
|
disabled={!$settings.playersite}
|
||||||
value={$settings.titel}
|
value={$settings.title}
|
||||||
onChange={handleChange('titel')}
|
onChange={handleChange('title')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@ -409,6 +412,14 @@ export default function Playersite(props) {
|
|||||||
<Trans>Main page channel (index.html).</Trans>
|
<Trans>Main page channel (index.html).</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Checkbox
|
||||||
|
label={<Trans>Share button</Trans>}
|
||||||
|
checked={$settings.share}
|
||||||
|
disabled={!$settings.playersite}
|
||||||
|
onChange={handleChange('share')}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={<Trans>Support datarhei Restreamer</Trans>}
|
label={<Trans>Support datarhei Restreamer</Trans>}
|
||||||
@ -448,7 +459,9 @@ export default function Playersite(props) {
|
|||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
<Typography variant="caption">
|
<Typography variant="caption">
|
||||||
<Trans>Template to be used for creating the publication website. The delete button removes the selection from the system.</Trans>
|
<Trans>
|
||||||
|
Template to be used for creating the publication website. The delete button removes the selection from the system.
|
||||||
|
</Trans>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
@ -520,8 +533,8 @@ export default function Playersite(props) {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
label={<Trans>Headline</Trans>}
|
label={<Trans>Headline</Trans>}
|
||||||
value={$settings.textcolor_titel}
|
value={$settings.textcolor_title}
|
||||||
onChange={handleChange('textcolor_titel')}
|
onChange={handleChange('textcolor_title')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={6}>
|
<Grid item xs={6}>
|
||||||
|
|||||||
@ -111,39 +111,41 @@ export default function Edit(props) {
|
|||||||
setReady(true);
|
setReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (what, section = '') => (event) => {
|
const handleChange =
|
||||||
const value = event.target.value;
|
(what, section = '') =>
|
||||||
const settings = $settings;
|
(event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
const settings = $settings;
|
||||||
|
|
||||||
if (section === '') {
|
if (section === '') {
|
||||||
if (['autoplay', 'mute', 'statistics'].includes(what)) {
|
if (['autoplay', 'mute', 'statistics'].includes(what)) {
|
||||||
settings[what] = !settings[what];
|
settings[what] = !settings[what];
|
||||||
} else {
|
} else {
|
||||||
settings[what] = value;
|
settings[what] = value;
|
||||||
|
}
|
||||||
|
} else if (section === 'color') {
|
||||||
|
settings.color[what] = value;
|
||||||
|
} else if (section === 'ga') {
|
||||||
|
settings.ga[what] = value;
|
||||||
|
} else if (section === 'logo') {
|
||||||
|
settings.logo[what] = value;
|
||||||
}
|
}
|
||||||
} else if (section === 'color') {
|
|
||||||
settings.color[what] = value;
|
|
||||||
} else if (section === 'ga') {
|
|
||||||
settings.ga[what] = value;
|
|
||||||
} else if (section === 'logo') {
|
|
||||||
settings.logo[what] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout.current !== null) {
|
if (timeout.current !== null) {
|
||||||
clearTimeout(timeout.current);
|
clearTimeout(timeout.current);
|
||||||
timeout.current = null;
|
timeout.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
timeout.current = setTimeout(() => {
|
timeout.current = setTimeout(() => {
|
||||||
timeout.current = null;
|
timeout.current = null;
|
||||||
setRevision($revision + 1);
|
setRevision($revision + 1);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
setSettings({
|
setSettings({
|
||||||
...$settings,
|
...$settings,
|
||||||
...settings,
|
...settings,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogoUpload = (event) => {
|
const handleLogoUpload = (event) => {
|
||||||
const handler = (event) => {
|
const handler = (event) => {
|
||||||
@ -171,9 +173,10 @@ export default function Edit(props) {
|
|||||||
if (type === null) {
|
if (type === null) {
|
||||||
// not one of the allowed mimetypes
|
// not one of the allowed mimetypes
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
const types = logoAcceptString;
|
||||||
showUploadError(
|
showUploadError(
|
||||||
<Trans>
|
<Trans>
|
||||||
The selected file type ({file.type}) is now allowed. Allowed file types are {logoAcceptString}
|
The selected file type ({file.type}) is not allowed. Allowed file types are {types}
|
||||||
</Trans>
|
</Trans>
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -5,9 +5,10 @@ import Grid from '@mui/material/Grid';
|
|||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
import Checkbox from '../../../misc/Checkbox';
|
|
||||||
import Logo from './logos/akamai.svg';
|
import Logo from './logos/akamai.svg';
|
||||||
|
|
||||||
|
import Checkbox from '../../../misc/Checkbox';
|
||||||
|
|
||||||
const id = 'akamai';
|
const id = 'akamai';
|
||||||
const name = 'Akamai';
|
const name = 'Akamai';
|
||||||
const version = '1.0';
|
const version = '1.0';
|
||||||
|
|||||||
@ -13,8 +13,7 @@ const version = '1.0';
|
|||||||
const stream_key_link = '';
|
const stream_key_link = '';
|
||||||
const description = (
|
const description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
Transmit the main source to an CDN77 RTMP Service.
|
Transmit the main source to an CDN77 RTMP Service. More about the setup{' '}
|
||||||
More about the setup{' '}
|
|
||||||
<Link color="secondary" target="_blank" href="https://client.cdn77.com/support/knowledgebase/streaming/ls_general_setup">
|
<Link color="secondary" target="_blank" href="https://client.cdn77.com/support/knowledgebase/streaming/ls_general_setup">
|
||||||
here
|
here
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -6,13 +6,14 @@ import Link from '@mui/material/Link';
|
|||||||
import MenuItem from '@mui/material/MenuItem';
|
import MenuItem from '@mui/material/MenuItem';
|
||||||
import TextField from '@mui/material/TextField';
|
import TextField from '@mui/material/TextField';
|
||||||
|
|
||||||
|
import FormInlineButton from '../../../misc/FormInlineButton';
|
||||||
import Logo from './logos/datarhei.svg';
|
import Logo from './logos/datarhei.svg';
|
||||||
import Select from '../../../misc/Select';
|
import Select from '../../../misc/Select';
|
||||||
|
|
||||||
const id = 'datarhei_core';
|
const id = 'datarhei_core';
|
||||||
const name = 'datarhei Core';
|
const name = 'datarhei Core';
|
||||||
const version = '1.0';
|
const version = '1.0';
|
||||||
const stream_key_link = '';
|
const stream_key_link = 'https://docs.datarhei.com/restreamer/knowledge-base/manual/system-settings/rtmp';
|
||||||
const description = (
|
const description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
Transmit the main source to an datarhei Core Ressource. More details about the settings can be found{' '}
|
Transmit the main source to an datarhei Core Ressource. More details about the settings can be found{' '}
|
||||||
@ -77,7 +78,7 @@ function Service(props) {
|
|||||||
const options = ['-f', 'flv'];
|
const options = ['-f', 'flv'];
|
||||||
|
|
||||||
const output = {
|
const output = {
|
||||||
address: settings.protocol + settings.base_url + settings.app_path + settings.stream_name + '?token=' + settings.rtmp_token,
|
address: settings.protocol + settings.base_url + '/' + settings.app_path + '/' + settings.stream_name + '?token=' + settings.rtmp_token,
|
||||||
options: options,
|
options: options,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,13 +104,7 @@ function Service(props) {
|
|||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
<TextField
|
<TextField variant="outlined" fullWidth label={<Trans>App</Trans>} value={settings.app_path} onChange={handleChange('app_path')} />
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
label={<Trans>App</Trans>}
|
|
||||||
value={settings.app_path}
|
|
||||||
onChange={handleChange('app_path')}
|
|
||||||
/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<TextField
|
<TextField
|
||||||
@ -120,7 +115,7 @@ function Service(props) {
|
|||||||
onChange={handleChange('stream_name')}
|
onChange={handleChange('stream_name')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={9}>
|
||||||
<TextField
|
<TextField
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -129,6 +124,11 @@ function Service(props) {
|
|||||||
onChange={handleChange('rtmp_token')}
|
onChange={handleChange('rtmp_token')}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<FormInlineButton target="blank" href={stream_key_link} component="a">
|
||||||
|
<Trans>GET</Trans>
|
||||||
|
</FormInlineButton>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,11 +111,7 @@ function Service(props) {
|
|||||||
)}
|
)}
|
||||||
{settings.rtmp_primary === true && (
|
{settings.rtmp_primary === true && (
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
<FormInlineButton
|
<FormInlineButton target="blank" href={stream_key_link} component="a">
|
||||||
target="blank"
|
|
||||||
href={stream_key_link}
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<Trans>GET</Trans>
|
<Trans>GET</Trans>
|
||||||
</FormInlineButton>
|
</FormInlineButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -133,11 +129,7 @@ function Service(props) {
|
|||||||
)}
|
)}
|
||||||
{settings.rtmp_backup === true && (
|
{settings.rtmp_backup === true && (
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
<FormInlineButton
|
<FormInlineButton target="blank" href={stream_key_link} component="a">
|
||||||
target="blank"
|
|
||||||
href={stream_key_link}
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<Trans>GET</Trans>
|
<Trans>GET</Trans>
|
||||||
</FormInlineButton>
|
</FormInlineButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ const version = '1.0';
|
|||||||
const stream_key_link = '';
|
const stream_key_link = '';
|
||||||
const description = (
|
const description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
Transmit the main source to an Red5/Pro Server. More details about the settings can be found {' '}
|
Transmit the main source to an Red5/Pro Server. More details about the settings can be found{' '}
|
||||||
<Link color="secondary" target="_blank" href="https://www.red5pro.com/docs/server/ffmpeg/publishing-live-streams/">
|
<Link color="secondary" target="_blank" href="https://www.red5pro.com/docs/server/ffmpeg/publishing-live-streams/">
|
||||||
here
|
here
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -116,11 +116,7 @@ function Service(props) {
|
|||||||
<TextField variant="outlined" fullWidth label={<Trans>Stream key</Trans>} value={settings.stream_key} onChange={handleChange('stream_key')} />
|
<TextField variant="outlined" fullWidth label={<Trans>Stream key</Trans>} value={settings.stream_key} onChange={handleChange('stream_key')} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item cs={12} md={3}>
|
<Grid item cs={12} md={3}>
|
||||||
<FormInlineButton
|
<FormInlineButton target="blank" href={stream_key_link} component="a">
|
||||||
target="blank"
|
|
||||||
href={stream_key_link}
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<Trans>GET</Trans>
|
<Trans>GET</Trans>
|
||||||
</FormInlineButton>
|
</FormInlineButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -129,11 +129,7 @@ function Service(props) {
|
|||||||
<TextField variant="outlined" fullWidth label={<Trans>Stream key</Trans>} value={settings.key} onChange={handleChange('key')} />
|
<TextField variant="outlined" fullWidth label={<Trans>Stream key</Trans>} value={settings.key} onChange={handleChange('key')} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={3}>
|
||||||
<FormInlineButton
|
<FormInlineButton target="blank" href={stream_key_link} component="a">
|
||||||
target="blank"
|
|
||||||
href={stream_key_link}
|
|
||||||
component="a"
|
|
||||||
>
|
|
||||||
<Trans>GET</Trans>
|
<Trans>GET</Trans>
|
||||||
</FormInlineButton>
|
</FormInlineButton>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@ -18,8 +18,8 @@ const version = '1.0';
|
|||||||
const stream_key_link = 'https://studio.twitter.com/producer/sources';
|
const stream_key_link = 'https://studio.twitter.com/producer/sources';
|
||||||
const description = (
|
const description = (
|
||||||
<Trans>
|
<Trans>
|
||||||
Transmits your video stream with the required key, which was generated in Twitter Producer. You can find more information on seting up a live stream
|
Transmits your video stream with the required key, which was generated in Twitter Producer. You can find more information on seting up a live stream at
|
||||||
at Twitter's{' '}
|
Twitter's{' '}
|
||||||
<Link color="secondary" target="_blank" href="https://help.twitter.com/en/using-twitter/how-to-use-live-producer">
|
<Link color="secondary" target="_blank" href="https://help.twitter.com/en/using-twitter/how-to-use-live-producer">
|
||||||
Producer
|
Producer
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import TextField from '@mui/material/TextField';
|
|||||||
const id = 'vimeo';
|
const id = 'vimeo';
|
||||||
const name = 'Vimeo';
|
const name = 'Vimeo';
|
||||||
const version = '1.0';
|
const version = '1.0';
|
||||||
const description = <Trans>Live-Streaming to Vimeos Live RTMP Service</Trans>;
|
const description = <Trans>Live-Streaming to Vimeo Live RTMP Service</Trans>;
|
||||||
const stream_key_link = '';
|
const stream_key_link = '';
|
||||||
const image_copyright = <Trans>More about licenses here</Trans>;
|
const image_copyright = <Trans>More about licenses here</Trans>;
|
||||||
const author = {
|
const author = {
|
||||||
|
|||||||
@ -1173,7 +1173,12 @@ export default function Settings(props) {
|
|||||||
{$updates.has === true && (
|
{$updates.has === true && (
|
||||||
<BoxText color="success">
|
<BoxText color="success">
|
||||||
<Typography variant="inherit" color="inherit" width="100%">
|
<Typography variant="inherit" color="inherit" width="100%">
|
||||||
<Link color="inherit" style={{ textDecoration: 'underline' }} onClick={handleHelp('update-link')} target="_blank">
|
<Link
|
||||||
|
color="inherit"
|
||||||
|
style={{ textDecoration: 'underline', cursor: 'pointer' }}
|
||||||
|
onClick={handleHelp('update-link')}
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
<Trans>There are updates available. Here you get more information.</Trans>
|
<Trans>There are updates available. Here you get more information.</Trans>
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user