Custom thumbnail poster component (#281)
* Custom thumbnail poster component * add opacity transition to thumbnail img * fix some videoonly styles * move video styles to video.css * make component out of image layers; put inline styles into css * cleanup * update videoonly ; don't render poster if video player, remove dom modification in player * revert interval Co-authored-by: Ginger Wong <omqmail@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { h, Component } from '/js/web_modules/preact.js';
|
|||||||
import htm from '/js/web_modules/htm.js';
|
import htm from '/js/web_modules/htm.js';
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
import VideoPoster from './components/video-poster.js';
|
||||||
import { OwncastPlayer } from './components/player.js';
|
import { OwncastPlayer } from './components/player.js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -28,6 +29,8 @@ export default class VideoOnly extends Component {
|
|||||||
playerActive: false, // player object is active
|
playerActive: false, // player object is active
|
||||||
streamOnline: false, // stream is active/online
|
streamOnline: false, // stream is active/online
|
||||||
|
|
||||||
|
isPlaying: false,
|
||||||
|
|
||||||
//status
|
//status
|
||||||
streamStatusMessage: MESSAGE_OFFLINE,
|
streamStatusMessage: MESSAGE_OFFLINE,
|
||||||
viewerCount: '',
|
viewerCount: '',
|
||||||
@@ -141,12 +144,6 @@ export default class VideoOnly extends Component {
|
|||||||
// stream has just flipped offline.
|
// stream has just flipped offline.
|
||||||
this.handleOfflineMode();
|
this.handleOfflineMode();
|
||||||
}
|
}
|
||||||
if (status.online) {
|
|
||||||
// only do this if video is paused, so no unnecessary img fetches
|
|
||||||
if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) {
|
|
||||||
this.player.setPoster();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
viewerCount,
|
viewerCount,
|
||||||
streamOnline: online,
|
streamOnline: online,
|
||||||
@@ -160,7 +157,9 @@ export default class VideoOnly extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePlayerPlaying() {
|
handlePlayerPlaying() {
|
||||||
// do something?
|
this.setState({
|
||||||
|
isPlaying: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// likely called some time after stream status has gone offline.
|
// likely called some time after stream status has gone offline.
|
||||||
@@ -168,6 +167,7 @@ export default class VideoOnly extends Component {
|
|||||||
handlePlayerEnded() {
|
handlePlayerEnded() {
|
||||||
this.setState({
|
this.setState({
|
||||||
playerActive: false,
|
playerActive: false,
|
||||||
|
isPlaying: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,29 +212,26 @@ export default class VideoOnly extends Component {
|
|||||||
playerActive,
|
playerActive,
|
||||||
streamOnline,
|
streamOnline,
|
||||||
streamStatusMessage,
|
streamStatusMessage,
|
||||||
|
isPlaying,
|
||||||
} = state;
|
} = state;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
version: appVersion,
|
|
||||||
logo = {},
|
logo = {},
|
||||||
socialHandles = [],
|
|
||||||
name: streamerName,
|
|
||||||
summary,
|
|
||||||
tags = [],
|
|
||||||
title,
|
|
||||||
} = configData;
|
} = configData;
|
||||||
const { small: smallLogo = TEMP_IMAGE, large: largeLogo = TEMP_IMAGE } = logo;
|
const { large: largeLogo = TEMP_IMAGE } = logo;
|
||||||
|
const streamInfoClass = streamOnline ? 'online' : ''; // need?
|
||||||
const bgLogoLarge = { backgroundImage: `url(${largeLogo})` };
|
|
||||||
|
|
||||||
const mainClass = playerActive ? 'online' : '';
|
const mainClass = playerActive ? 'online' : '';
|
||||||
|
|
||||||
|
const poster = isPlaying ? null : html`
|
||||||
|
<${VideoPoster} offlineImage=${largeLogo} active=${streamOnline} />
|
||||||
|
`;
|
||||||
return (
|
return (
|
||||||
html`
|
html`
|
||||||
<main class=${mainClass}>
|
<main class=${mainClass}>
|
||||||
<div
|
<div
|
||||||
id="video-container"
|
id="video-container"
|
||||||
class="flex owncast-video-container bg-black w-full bg-center bg-no-repeat flex flex-col items-center justify-start"
|
class="flex owncast-video-container bg-black w-full bg-center bg-no-repeat flex flex-col items-center justify-start"
|
||||||
style=${bgLogoLarge}
|
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
class="video-js vjs-big-play-centered display-block w-full h-full"
|
class="video-js vjs-big-play-centered display-block w-full h-full"
|
||||||
@@ -243,9 +240,14 @@ export default class VideoOnly extends Component {
|
|||||||
controls
|
controls
|
||||||
playsinline
|
playsinline
|
||||||
></video>
|
></video>
|
||||||
|
${poster}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="stream-info" aria-label="Stream status" class="flex text-center flex-row justify-between items-center font-mono py-2 px-8 bg-gray-900 text-indigo-200">
|
<section
|
||||||
|
id="stream-info"
|
||||||
|
aria-label="Stream status"
|
||||||
|
class="flex text-center flex-row justify-between font-mono py-2 px-8 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid ${streamInfoClass}"
|
||||||
|
>
|
||||||
<span>${streamStatusMessage}</span>
|
<span>${streamStatusMessage}</span>
|
||||||
<span>${viewerCount} ${pluralize('viewer', viewerCount)}.</span>
|
<span>${viewerCount} ${pluralize('viewer', viewerCount)}.</span>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const html = htm.bind(h);
|
|||||||
import { OwncastPlayer } from './components/player.js';
|
import { OwncastPlayer } from './components/player.js';
|
||||||
import SocialIconsList from './components/social-icons-list.js';
|
import SocialIconsList from './components/social-icons-list.js';
|
||||||
import UsernameForm from './components/chat/username.js';
|
import UsernameForm from './components/chat/username.js';
|
||||||
|
import VideoPoster from './components/video-poster.js';
|
||||||
import Chat from './components/chat/chat.js';
|
import Chat from './components/chat/chat.js';
|
||||||
import Websocket from './utils/websocket.js';
|
import Websocket from './utils/websocket.js';
|
||||||
import { secondsToHMMSS, hasTouchScreen, getOrientation } from './utils/helpers.js';
|
import { secondsToHMMSS, hasTouchScreen, getOrientation } from './utils/helpers.js';
|
||||||
@@ -54,6 +55,7 @@ export default class App extends Component {
|
|||||||
|
|
||||||
playerActive: false, // player object is active
|
playerActive: false, // player object is active
|
||||||
streamOnline: false, // stream is active/online
|
streamOnline: false, // stream is active/online
|
||||||
|
isPlaying: false, // player is actively playing video
|
||||||
|
|
||||||
// status
|
// status
|
||||||
streamStatusMessage: MESSAGE_OFFLINE,
|
streamStatusMessage: MESSAGE_OFFLINE,
|
||||||
@@ -191,12 +193,7 @@ export default class App extends Component {
|
|||||||
// stream has just flipped offline.
|
// stream has just flipped offline.
|
||||||
this.handleOfflineMode();
|
this.handleOfflineMode();
|
||||||
}
|
}
|
||||||
if (status.online) {
|
|
||||||
// only do this if video is paused, so no unnecessary img fetches
|
|
||||||
if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) {
|
|
||||||
this.player.setPoster();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.setState({
|
this.setState({
|
||||||
viewerCount,
|
viewerCount,
|
||||||
lastConnectTime,
|
lastConnectTime,
|
||||||
@@ -211,7 +208,9 @@ export default class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePlayerPlaying() {
|
handlePlayerPlaying() {
|
||||||
// do something?
|
this.setState({
|
||||||
|
isPlaying: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// likely called some time after stream status has gone offline.
|
// likely called some time after stream status has gone offline.
|
||||||
@@ -219,6 +218,7 @@ export default class App extends Component {
|
|||||||
handlePlayerEnded() {
|
handlePlayerEnded() {
|
||||||
this.setState({
|
this.setState({
|
||||||
playerActive: false,
|
playerActive: false,
|
||||||
|
isPlaying: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,6 +320,7 @@ export default class App extends Component {
|
|||||||
chatInputEnabled,
|
chatInputEnabled,
|
||||||
configData,
|
configData,
|
||||||
displayChat,
|
displayChat,
|
||||||
|
isPlaying,
|
||||||
orientation,
|
orientation,
|
||||||
playerActive,
|
playerActive,
|
||||||
streamOnline,
|
streamOnline,
|
||||||
@@ -368,7 +369,6 @@ export default class App extends Component {
|
|||||||
|
|
||||||
const mainClass = playerActive ? 'online' : '';
|
const mainClass = playerActive ? 'online' : '';
|
||||||
const streamInfoClass = streamOnline ? 'online' : ''; // need?
|
const streamInfoClass = streamOnline ? 'online' : ''; // need?
|
||||||
|
|
||||||
const isPortrait = this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT;
|
const isPortrait = this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT;
|
||||||
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait;
|
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait;
|
||||||
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
|
||||||
@@ -382,6 +382,10 @@ export default class App extends Component {
|
|||||||
'touch-screen': this.hasTouchScreen,
|
'touch-screen': this.hasTouchScreen,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const poster = isPlaying ? null : html`
|
||||||
|
<${VideoPoster} offlineImage=${largeLogo} active=${streamOnline} />
|
||||||
|
`;
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
id="app-container"
|
id="app-container"
|
||||||
@@ -429,7 +433,6 @@ export default class App extends Component {
|
|||||||
<div
|
<div
|
||||||
id="video-container"
|
id="video-container"
|
||||||
class="flex owncast-video-container bg-black w-full bg-center bg-no-repeat flex flex-col items-center justify-start"
|
class="flex owncast-video-container bg-black w-full bg-center bg-no-repeat flex flex-col items-center justify-start"
|
||||||
style=${bgLogoLarge}
|
|
||||||
>
|
>
|
||||||
<video
|
<video
|
||||||
class="video-js vjs-big-play-centered display-block w-full h-full"
|
class="video-js vjs-big-play-centered display-block w-full h-full"
|
||||||
@@ -438,6 +441,7 @@ export default class App extends Component {
|
|||||||
controls
|
controls
|
||||||
playsinline
|
playsinline
|
||||||
></video>
|
></video>
|
||||||
|
${poster}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|||||||
@@ -118,7 +118,6 @@ class OwncastPlayer {
|
|||||||
if (this.appPlayerEndedCallback) {
|
if (this.appPlayerEndedCallback) {
|
||||||
this.appPlayerEndedCallback();
|
this.appPlayerEndedCallback();
|
||||||
}
|
}
|
||||||
this.setPoster();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleError(e) {
|
handleError(e) {
|
||||||
@@ -128,13 +127,6 @@ class OwncastPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setPoster() {
|
|
||||||
const cachebuster = Math.round(new Date().getTime() / 1000);
|
|
||||||
const poster = POSTER_THUMB + '?okhi=' + cachebuster;
|
|
||||||
|
|
||||||
this.vjsPlayer.poster(poster);
|
|
||||||
}
|
|
||||||
|
|
||||||
log(message) {
|
log(message) {
|
||||||
// console.log(`>>> Player: ${message}`);
|
// console.log(`>>> Player: ${message}`);
|
||||||
}
|
}
|
||||||
|
|||||||
114
webroot/js/components/video-poster.js
Normal file
114
webroot/js/components/video-poster.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { h, Component } from '/js/web_modules/preact.js';
|
||||||
|
import htm from '/js/web_modules/htm.js';
|
||||||
|
const html = htm.bind(h);
|
||||||
|
|
||||||
|
import { TEMP_IMAGE } from '../utils/constants.js';
|
||||||
|
|
||||||
|
const REFRESH_INTERVAL = 15000;
|
||||||
|
const POSTER_BASE_URL = '/thumbnail.jpg';
|
||||||
|
|
||||||
|
export default class VideoPoster extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
// flipped is the state of showing primary/secondary image views
|
||||||
|
flipped: false,
|
||||||
|
oldUrl: TEMP_IMAGE,
|
||||||
|
url: TEMP_IMAGE,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.refreshTimer = null;
|
||||||
|
this.startRefreshTimer = this.startRefreshTimer.bind(this);
|
||||||
|
this.fire = this.fire.bind(this);
|
||||||
|
this.setLoaded = this.setLoaded.bind(this);
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.active) {
|
||||||
|
this.fire();
|
||||||
|
this.startRefreshTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shouldComponentUpdate(prevProps, prevState) {
|
||||||
|
return this.props.active !== prevProps.active ||
|
||||||
|
this.props.offlineImage !== prevProps.offlineImage ||
|
||||||
|
this.state.url !== prevState.url ||
|
||||||
|
this.state.oldUrl !== prevState.oldUrl;
|
||||||
|
}
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { active } = this.props;
|
||||||
|
const { active: prevActive } = prevProps;
|
||||||
|
|
||||||
|
if (active && !prevActive) {
|
||||||
|
this.startRefreshTimer();
|
||||||
|
} else if (!active && prevActive) {
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
startRefreshTimer() {
|
||||||
|
this.stopRefreshTimer();
|
||||||
|
this.fire();
|
||||||
|
// Load a new copy of the image every n seconds
|
||||||
|
this.refreshTimer = setInterval(this.fire, REFRESH_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load new img
|
||||||
|
fire() {
|
||||||
|
const cachebuster = Math.round(new Date().getTime() / 1000);
|
||||||
|
this.loadingImage = POSTER_BASE_URL + '?cb=' + cachebuster;
|
||||||
|
const img = new Image();
|
||||||
|
img.onload = this.setLoaded;
|
||||||
|
img.src = this.loadingImage;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoaded() {
|
||||||
|
const { url: currentUrl, flipped } = this.state;
|
||||||
|
this.setState({
|
||||||
|
flipped: !flipped,
|
||||||
|
url: this.loadingImage,
|
||||||
|
oldUrl: currentUrl,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopRefreshTimer() {
|
||||||
|
clearInterval(this.refreshTimer);
|
||||||
|
this.refreshTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { active, offlineImage } = this.props;
|
||||||
|
const { url, oldUrl, flipped } = this.state;
|
||||||
|
if (!active) {
|
||||||
|
return html`
|
||||||
|
<div id="oc-custom-poster">
|
||||||
|
<${ThumbImage} url=${offlineImage} visible=${true} />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div id="oc-custom-poster">
|
||||||
|
<${ThumbImage} url=${!flipped ? oldUrl : url } visible=${true} />
|
||||||
|
<${ThumbImage} url=${flipped ? oldUrl : url } visible=${!flipped} />
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ThumbImage({ url, visible }) {
|
||||||
|
if (!url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="custom-thumbnail-image"
|
||||||
|
style=${{
|
||||||
|
opacity: visible ? 1 : 0,
|
||||||
|
backgroundImage: `url(${url})`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
`;
|
||||||
|
}
|
||||||
@@ -97,6 +97,10 @@ header {
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#app-container .custom-thumbnail-image {
|
||||||
|
transition: opacity 2s;
|
||||||
|
}
|
||||||
|
|
||||||
/* *********** overrides when chat is off ***************************** */
|
/* *********** overrides when chat is off ***************************** */
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,21 +37,19 @@
|
|||||||
/******************************/
|
/******************************/
|
||||||
/******************************/
|
/******************************/
|
||||||
|
|
||||||
|
|
||||||
#message-input img {
|
#message-input img {
|
||||||
display: inline;
|
display: inline;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
padding: .25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-input .emoji {
|
#message-input .emoji {
|
||||||
width: 2.2rem;
|
width: 2.2rem;
|
||||||
padding: .25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* If the div is empty then show the placeholder */
|
/* If the div is empty then show the placeholder */
|
||||||
#message-input:empty:before{
|
#message-input:empty:before {
|
||||||
content: attr(placeholderText);
|
content: attr(placeholderText);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
display: block; /* For Firefox */
|
display: block; /* For Firefox */
|
||||||
@@ -59,34 +57,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* When chat is enabled (contenteditable=true) */
|
/* When chat is enabled (contenteditable=true) */
|
||||||
#message-input[contenteditable=true]:before {
|
#message-input[contenteditable='true']:before {
|
||||||
opacity: 1.0;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#message-input::selection { background:#d7ddf4; }
|
#message-input::selection {
|
||||||
|
background: #d7ddf4;
|
||||||
|
}
|
||||||
|
|
||||||
/* When chat is disabled (contenteditable=false) chat input div should appear disabled. */
|
/* When chat is disabled (contenteditable=false) chat input div should appear disabled. */
|
||||||
#message-input:disabled,
|
#message-input:disabled,
|
||||||
#message-input[contenteditable=false] {
|
#message-input[contenteditable='false'] {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
/******************************/
|
/******************************/
|
||||||
/******************************/
|
/******************************/
|
||||||
|
|
||||||
|
|
||||||
/******************************/
|
/******************************/
|
||||||
/* EMOJI PICKER OVERRIDES */
|
/* EMOJI PICKER OVERRIDES */
|
||||||
.emoji-picker.owncast {
|
.emoji-picker.owncast {
|
||||||
--secondary-text-color: rgba(255,255,255,.5);
|
--secondary-text-color: rgba(255, 255, 255, 0.5);
|
||||||
--category-button-color: rgba(255,255,255,.5);
|
--category-button-color: rgba(255, 255, 255, 0.5);
|
||||||
--hover-color: rgba(255,255,255,.25);
|
--hover-color: rgba(255, 255, 255, 0.25);
|
||||||
|
|
||||||
background: rgba(26,32,44,1); /* tailwind bg-gray-900 */
|
background: rgba(26, 32, 44, 1); /* tailwind bg-gray-900 */
|
||||||
color: rgba(226,232,240,1); /* tailwind text-gray-300 */
|
color: rgba(226, 232, 240, 1); /* tailwind text-gray-300 */
|
||||||
border-color: black;
|
border-color: black;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
|
||||||
}
|
}
|
||||||
.emoji-picker h2 {
|
.emoji-picker h2 {
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
@@ -103,7 +100,7 @@
|
|||||||
.emoji-picker__emojis::-webkit-scrollbar-track {
|
.emoji-picker__emojis::-webkit-scrollbar-track {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
box-shadow: inset 0 0 3px rgba(0,0,0,0.3);
|
box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji-picker__emojis::-webkit-scrollbar-thumb {
|
.emoji-picker__emojis::-webkit-scrollbar-thumb {
|
||||||
@@ -122,7 +119,7 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
.message-text a {
|
.message-text a {
|
||||||
color: #7F9CF5; /* indigo-400 */
|
color: #7f9cf5; /* indigo-400 */
|
||||||
}
|
}
|
||||||
.message-text a:hover {
|
.message-text a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
@@ -130,34 +127,31 @@
|
|||||||
|
|
||||||
.message-text img {
|
.message-text img {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding-left: 0 .25rem;
|
padding-left: 0 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.message-text .emoji {
|
.message-text .emoji {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -5px;
|
top: -5px;
|
||||||
width: 3rem;
|
width: 3rem;
|
||||||
padding: .25rem
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text code {
|
.message-text code {
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
background-color:darkslategrey;
|
background-color: darkslategrey;
|
||||||
padding: .25rem;
|
padding: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text .chat-embed {
|
.message-text .chat-embed {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: .25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text .instagram-embed {
|
.message-text .instagram-embed {
|
||||||
height: 24rem;
|
height: 24rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.message-text .embedded-image {
|
.message-text .embedded-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: block;
|
display: block;
|
||||||
@@ -174,11 +168,3 @@
|
|||||||
/* MESSAGE TEXT CONTENT */
|
/* MESSAGE TEXT CONTENT */
|
||||||
/* MESSAGE TEXT CONTENT */
|
/* MESSAGE TEXT CONTENT */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ The styles in this file mostly ovveride those coming from chat.css
|
|||||||
background-size: 30%;
|
background-size: 30%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc((9 / 16) * 100vw);
|
height: calc((9 / 16) * 100vw);
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
#video-only #video-container #video {
|
#video-only #video-container #video {
|
||||||
transition: opacity .5s;
|
transition: opacity .5s;
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ video.video-js {
|
|||||||
content: url("../img/airplay.png");
|
content: url("../img/airplay.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vjs-big-play-button {
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
videojs hack!!
|
videojs hack!!
|
||||||
@@ -25,3 +28,26 @@ https://github.com/owncast/owncast/issues/165
|
|||||||
video.vjs-tech:not([src]) {
|
video.vjs-tech:not([src]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#oc-custom-poster {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation time for crossfading between poster thumbs */
|
||||||
|
.custom-thumbnail-image {
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user