Merge branch 'dev' into av_filter
This commit is contained in:
commit
827f5bec54
@ -6,6 +6,8 @@
|
||||
- Add video h/v flip filter
|
||||
- Add audio volume filter ([#313](https://github.com/datarhei/restreamer/issues/313))
|
||||
- Add audio loudness normalization filter
|
||||
- Add AirPlay support with silvermine videojs plugin
|
||||
- Add Chromecast support (thx badincite, [#10](https://github.com/datarhei/restreamer-ui/pull/10))
|
||||
- Add stream distribution across multiple internal servers
|
||||
- Add SRT settings
|
||||
- Add HLS version selection (Dwaynarang, Electra Player compatibility)
|
||||
|
||||
15
public/_player/videojs/dist/ic_airplay_white_24px.svg
vendored
Normal file
15
public/_player/videojs/dist/ic_airplay_white_24px.svg
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<path d="M0 0h24v24H0V0z" id="a"/>
|
||||
</defs>
|
||||
<defs>
|
||||
<path d="M0 0h24v24H0V0z" id="c"/>
|
||||
</defs>
|
||||
<clipPath id="b">
|
||||
<use overflow="visible" xlink:href="#a"/>
|
||||
</clipPath>
|
||||
<clipPath clip-path="url(#b)" id="d">
|
||||
<use overflow="visible" xlink:href="#c"/>
|
||||
</clipPath>
|
||||
<path clip-path="url(#d)" d="M6 22h12l-6-6zM21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4v-2H3V5h18v12h-4v2h4c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 623 B |
BIN
public/_player/videojs/dist/ic_cast_connected_white_24dp.png
vendored
Normal file
BIN
public/_player/videojs/dist/ic_cast_connected_white_24dp.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 981 B |
BIN
public/_player/videojs/dist/ic_cast_white_24dp.png
vendored
Normal file
BIN
public/_player/videojs/dist/ic_cast_white_24dp.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
1
public/_player/videojs/dist/videojs-airplay.min.css
vendored
Normal file
1
public/_player/videojs/dist/videojs-airplay.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:20px;height:20px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}
|
||||
1315
public/_player/videojs/dist/videojs-airplay.min.js
vendored
Normal file
1315
public/_player/videojs/dist/videojs-airplay.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
65
public/_player/videojs/dist/videojs-chromecast.css
vendored
Normal file
65
public/_player/videojs/dist/videojs-chromecast.css
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
/** Silvermine Chromecast **/
|
||||
.vjs-chromecast-button .vjs-icon-placeholder {
|
||||
background: url('ic_cast_white_24dp.png') center center no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.vjs-chromecast-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.vjs-chromecast-button:hover .vjs-icon-placeholder {
|
||||
background-image: url('ic_cast_white_24dp.png');
|
||||
}
|
||||
.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder {
|
||||
background-image: url('ic_cast_connected_white_24dp.png');
|
||||
}
|
||||
.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder {
|
||||
background-image: url('ic_cast_connected_white_24dp.png');
|
||||
}
|
||||
.vjs-tech-chromecast {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-poster::after {
|
||||
content: ' ';
|
||||
display: block;
|
||||
height: 2px;
|
||||
width: 100px;
|
||||
background-color: #cccccc;
|
||||
position: absolute;
|
||||
left: calc(50% - 50px);
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-poster-img {
|
||||
max-height: 180px;
|
||||
width: auto;
|
||||
border: 2px solid #cccccc;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty {
|
||||
width: 160px;
|
||||
height: 90px;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-title-container {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
margin-bottom: 100px;
|
||||
color: #cccccc;
|
||||
text-align: center;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-title {
|
||||
font-size: 22px;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty {
|
||||
display: none;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-subtitle {
|
||||
font-size: 18px;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty {
|
||||
display: none;
|
||||
}
|
||||
1
public/_player/videojs/dist/videojs-chromecast.min.css
vendored
Normal file
1
public/_player/videojs/dist/videojs-chromecast.min.css
vendored
Normal file
@ -0,0 +1 @@
|
||||
.vjs-chromecast-button .vjs-icon-placeholder{background:url(ic_cast_white_24dp.png) center center no-repeat;background-size:contain;display:inline-block;width:20px;height:20px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url(ic_cast_white_24dp.png)}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url(ic_cast_connected_white_24dp.png)}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url(ic_cast_connected_white_24dp.png)}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:' ';display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}
|
||||
3147
public/_player/videojs/dist/videojs-chromecast.min.js
vendored
Normal file
3147
public/_player/videojs/dist/videojs-chromecast.min.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -5,3 +5,10 @@ dist/videojs-overlay.min.css
|
||||
dist/video-js-skin.min.css
|
||||
dist/videojs-license.min.js
|
||||
dist/videojs-license.min.css
|
||||
dist/videojs-chromecast.min.js
|
||||
dist/videojs-chromecast.min.css
|
||||
dist/ic_cast_connected_white_24dp.png
|
||||
dist/ic_cast_white_24dp.png
|
||||
dist/videojs-airplay.min.js
|
||||
dist/videojs-airplay.min.css
|
||||
dist/ic_airplay_white_24px.svg
|
||||
|
||||
@ -15,11 +15,12 @@
|
||||
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
||||
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
||||
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.player-poster[data-poster] .poster-background[data-poster] {
|
||||
height: initial !important;
|
||||
}
|
||||
</style>
|
||||
{{#if airplay}}
|
||||
<link href="player/videojs/dist/videojs-airplay.min.css" rel="stylesheet">
|
||||
{{/if}}
|
||||
{{#if chromecast}}
|
||||
<link href="player/videojs/dist/videojs-chromecast.min.css" rel="stylesheet">
|
||||
{{/if}}
|
||||
</head>
|
||||
<body>
|
||||
<div style="position:absolute;top:0;right:0;bottom:0;left:0">
|
||||
@ -28,6 +29,13 @@
|
||||
<script src="player/videojs/dist/video.min.js"></script>
|
||||
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
||||
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
||||
{{#if airplay}}
|
||||
<script src='player/videojs/dist/videojs-airplay.min.js'></script>
|
||||
{{/if}}
|
||||
{{#if chromecast}}
|
||||
<script src='player/videojs/dist/videojs-chromecast.min.js'></script>
|
||||
<script src='https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'></script>
|
||||
{{/if}}
|
||||
<script>
|
||||
function getQueryParam(key, defaultValue) {
|
||||
var query = window.location.search.substring(1);
|
||||
@ -80,6 +88,15 @@
|
||||
}
|
||||
};
|
||||
|
||||
if (playerConfig.chromecast) {
|
||||
config.techOrder = ["chromecast", "html5"];
|
||||
config.plugins.chromecast = {};
|
||||
}
|
||||
|
||||
if (playerConfig.airplay) {
|
||||
config.plugins.airPlay = {};
|
||||
}
|
||||
|
||||
var player = videojs('player', config);
|
||||
player.ready(function() {
|
||||
if(playerConfig.logo.image.length != 0) {
|
||||
|
||||
@ -44,6 +44,12 @@
|
||||
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
||||
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
||||
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
||||
{{#if airplay}}
|
||||
<link href="player/videojs/dist/videojs-airplay.min.css" rel="stylesheet">
|
||||
{{/if}}
|
||||
{{#if chromecast}}
|
||||
<link href="player/videojs/dist/videojs-chromecast.min.css" rel="stylesheet">
|
||||
{{/if}}
|
||||
{{/ifEquals}}
|
||||
<style>
|
||||
/** flexboxgrid 6.3.1 **/
|
||||
@ -782,6 +788,13 @@
|
||||
<script src="player/videojs/dist/video.min.js"></script>
|
||||
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
||||
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
||||
{{#if airplay}}
|
||||
<script src='player/videojs/dist/videojs-airplay.min.js'></script>
|
||||
{{/if}}
|
||||
{{#if chromecast}}
|
||||
<script src='player/videojs/dist/videojs-chromecast.min.js'></script>
|
||||
<script src='https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'></script>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<script src="player/clappr/clappr.min.js"></script>
|
||||
<script src="player/clappr/clappr-stats.min.js"></script>
|
||||
@ -824,6 +837,8 @@
|
||||
var mute = convertBoolParam("mute", playerConfig.mute);
|
||||
var statistics = convertBoolParam("stats", playerConfig.statistics);
|
||||
var color = convertColorParam("color", playerConfig.color.buttons);
|
||||
var chromecast = {{#if chromecast}}true{{else}}false{{/if}};
|
||||
var airplay = {{#if airplay}}true{{else}}false{{/if}};
|
||||
</script>
|
||||
<script src="playersite/player.js"></script>
|
||||
|
||||
|
||||
@ -2,17 +2,28 @@ var config = {
|
||||
controls: true,
|
||||
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
||||
autoplay: autoplay ? 'muted' : false,
|
||||
muted: mute,
|
||||
muted: true,
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
// Needed to append the url orgin in order for the source to properly pass to the cast device
|
||||
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
plugins: {
|
||||
license: playerConfig.license,
|
||||
},
|
||||
};
|
||||
|
||||
if (chromecast) {
|
||||
config.techOrder = ['chromecast', 'html5'];
|
||||
config.plugins.chromecast = {};
|
||||
}
|
||||
|
||||
if (airplay) {
|
||||
config.plugins.airPlay = {};
|
||||
}
|
||||
|
||||
var player = videojs('player', config);
|
||||
|
||||
player.ready(function () {
|
||||
if (playerConfig.logo.image.length != 0) {
|
||||
var overlay = null;
|
||||
|
||||
@ -358,7 +358,9 @@ class Restreamer {
|
||||
}
|
||||
|
||||
compatibility.core.have = this.Version().number;
|
||||
compatibility.ffmpeg.have = this.skills.ffmpeg.version;
|
||||
if (this.skills?.ffmpeg?.version) {
|
||||
compatibility.ffmpeg.have = this.skills.ffmpeg.version;
|
||||
}
|
||||
|
||||
compatibility.core.compatible = SemverSatisfies(compatibility.core.have, compatibility.core.want);
|
||||
compatibility.ffmpeg.compatible = SemverSatisfies(compatibility.ffmpeg.have, compatibility.ffmpeg.want);
|
||||
@ -371,8 +373,13 @@ class Restreamer {
|
||||
}
|
||||
|
||||
async _init() {
|
||||
await this._initConfig();
|
||||
const compatibility = this.Compatibility();
|
||||
if (!compatibility.compatible) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this._initSkills();
|
||||
await this._initConfig();
|
||||
await this._discoverChannels();
|
||||
}
|
||||
|
||||
@ -895,6 +902,10 @@ class Restreamer {
|
||||
}
|
||||
|
||||
ConfigOverrides(name) {
|
||||
if (!this.config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.config.overrides.includes(name);
|
||||
}
|
||||
|
||||
@ -1889,10 +1900,16 @@ class Restreamer {
|
||||
|
||||
// Set defaults for the settings of the selfhosted player
|
||||
InitPlayerSettings(initSettings) {
|
||||
if (!initSettings) {
|
||||
initSettings = {};
|
||||
}
|
||||
|
||||
const settings = {
|
||||
autoplay: false,
|
||||
mute: false,
|
||||
statistics: false,
|
||||
chromecast: false,
|
||||
airplay: false,
|
||||
color: {},
|
||||
ga: {},
|
||||
logo: {},
|
||||
@ -1936,6 +1953,8 @@ class Restreamer {
|
||||
return false;
|
||||
}
|
||||
|
||||
metadata.player = this.InitPlayerSettings(metadata.player);
|
||||
|
||||
const templateData = {
|
||||
channelid: channelid,
|
||||
name: metadata.meta.name,
|
||||
@ -1948,6 +1967,8 @@ class Restreamer {
|
||||
poster_url: this.GetIngestPosterUrlAddresses(channelid)[0],
|
||||
width: 640,
|
||||
height: 360,
|
||||
chromecast: metadata.player.chromecast,
|
||||
airplay: metadata.player.airplay,
|
||||
};
|
||||
|
||||
// upload player.html
|
||||
@ -1981,10 +2002,6 @@ class Restreamer {
|
||||
}
|
||||
|
||||
async UpdatePlayerConfig(channelid, metadata) {
|
||||
if (!('player' in metadata)) {
|
||||
metadata.player = {};
|
||||
}
|
||||
|
||||
metadata.player = this.InitPlayerSettings(metadata.player);
|
||||
|
||||
const playerConfig = {
|
||||
@ -2033,6 +2050,8 @@ class Restreamer {
|
||||
title: 'restreamer',
|
||||
share: true,
|
||||
support: true,
|
||||
chromecast: false,
|
||||
airplay: false,
|
||||
template: '!default',
|
||||
templatename: '',
|
||||
textcolor_title: 'rgba(255,255,255,1)',
|
||||
@ -2114,6 +2133,8 @@ class Restreamer {
|
||||
title: settings.title,
|
||||
share: settings.share,
|
||||
support: settings.support,
|
||||
chromecast: settings.chromecast,
|
||||
airplay: settings.airplay,
|
||||
url: this.GetPlayersiteUrl(),
|
||||
textcolor_title: settings.textcolor_title,
|
||||
textcolor_default: settings.textcolor_default,
|
||||
|
||||
@ -98,7 +98,7 @@ export default function Playersite(props) {
|
||||
const value = event.target.value;
|
||||
const settings = $settings;
|
||||
|
||||
if (['playersite', 'header', 'share', 'support'].includes(what)) {
|
||||
if (['playersite', 'header', 'share', 'support', 'chromecast', 'airplay'].includes(what)) {
|
||||
settings[what] = !settings[what];
|
||||
} else {
|
||||
settings[what] = value;
|
||||
@ -420,6 +420,22 @@ export default function Playersite(props) {
|
||||
onChange={handleChange('share')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>Chromecast</Trans>}
|
||||
checked={$settings.chromecast}
|
||||
disabled={!$settings.playersite}
|
||||
onChange={handleChange('chromecast')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>AirPlay</Trans>}
|
||||
checked={$settings.airplay}
|
||||
disabled={!$settings.playersite}
|
||||
onChange={handleChange('airplay')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>Support datarhei Restreamer</Trans>}
|
||||
|
||||
@ -118,7 +118,7 @@ export default function Edit(props) {
|
||||
const settings = $settings;
|
||||
|
||||
if (section === '') {
|
||||
if (['autoplay', 'mute', 'statistics'].includes(what)) {
|
||||
if (['autoplay', 'mute', 'statistics', 'chromecast', 'airplay'].includes(what)) {
|
||||
settings[what] = !settings[what];
|
||||
} else {
|
||||
settings[what] = value;
|
||||
@ -436,6 +436,8 @@ export default function Edit(props) {
|
||||
<Grid item xs={12}>
|
||||
<Checkbox label={<Trans>Autoplay</Trans>} checked={$settings.autoplay} onChange={handleChange('autoplay')} />
|
||||
<Checkbox label={<Trans>Mute</Trans>} checked={$settings.mute} onChange={handleChange('mute')} />
|
||||
<Checkbox label={<Trans>Chromecast</Trans>} checked={$settings.chromecast} onChange={handleChange('chromecast')} />
|
||||
<Checkbox label={<Trans>AirPlay</Trans>} checked={$settings.airplay} onChange={handleChange('airplay')} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TabPanel>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user