Browse Source

Merge branch 'master' into gek/current-stream-duration

pull/69/head
Ginger Wong 5 years ago
parent
commit
a3613612eb
  1. 61
      config/config.go
  2. 40
      config/defaults.go
  3. 1
      controllers/status.go
  4. 9
      core/chat/chat.go
  5. 1
      core/chat/client.go
  6. 20
      core/chat/server.go
  7. 3
      doc/config-example-full.yaml
  8. 38
      webroot/index.html
  9. 29
      webroot/js/app.js
  10. 43
      webroot/js/message.js
  11. 11
      webroot/js/utils.js
  12. 172
      webroot/styles/layout.css

61
config/config.go

@ -3,8 +3,6 @@ package config @@ -3,8 +3,6 @@ package config
import (
"errors"
"io/ioutil"
"os/exec"
"strings"
"github.com/gabek/owncast/utils"
log "github.com/sirupsen/logrus"
@ -13,6 +11,7 @@ import ( @@ -13,6 +11,7 @@ import (
//Config contains a reference to the configuration
var Config *config
var _default config
type config struct {
ChatDatabaseFilePath string `yaml:"chatDatabaseFile"`
@ -116,6 +115,10 @@ func (c *config) load(filePath string) error { @@ -116,6 +115,10 @@ func (c *config) load(filePath string) error {
}
func (c *config) verifySettings() error {
if c.VideoSettings.StreamingKey == "" {
return errors.New("No stream key set. Please set one in your config file.")
}
if c.S3.Enabled && c.IPFS.Enabled {
return errors.New("s3 and IPFS support cannot be enabled at the same time; choose one")
}
@ -137,32 +140,12 @@ func (c *config) verifySettings() error { @@ -137,32 +140,12 @@ func (c *config) verifySettings() error {
return nil
}
func (c *config) GetFFMpegPath() string {
if c.FFMpegPath != "" {
return c.FFMpegPath
}
cmd := exec.Command("which", "ffmpeg")
out, err := cmd.CombinedOutput()
if err != nil {
log.Panicln("Unable to determine path to ffmpeg. Please specify it in the config file.")
}
path := strings.TrimSpace(string(out))
// Memoize it for future access
c.FFMpegPath = path
return path
}
func (c *config) GetVideoSegmentSecondsLength() int {
if c.VideoSettings.ChunkLengthInSeconds != 0 {
return c.VideoSettings.ChunkLengthInSeconds
}
// Default
return 4
return _default.GetVideoSegmentSecondsLength()
}
func (c *config) GetPublicHLSSavePath() string {
@ -170,7 +153,7 @@ func (c *config) GetPublicHLSSavePath() string { @@ -170,7 +153,7 @@ func (c *config) GetPublicHLSSavePath() string {
return c.PublicHLSPath
}
return "webroot/hls"
return _default.PublicHLSPath
}
func (c *config) GetPrivateHLSSavePath() string {
@ -178,7 +161,7 @@ func (c *config) GetPrivateHLSSavePath() string { @@ -178,7 +161,7 @@ func (c *config) GetPrivateHLSSavePath() string {
return c.PrivateHLSPath
}
return "hls"
return _default.PrivateHLSPath
}
func (c *config) GetPublicWebServerPort() int {
@ -186,8 +169,7 @@ func (c *config) GetPublicWebServerPort() int { @@ -186,8 +169,7 @@ func (c *config) GetPublicWebServerPort() int {
return c.WebServerPort
}
// Default web server port
return 8080
return _default.WebServerPort
}
func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int {
@ -195,7 +177,7 @@ func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int { @@ -195,7 +177,7 @@ func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int {
return c.Files.MaxNumberInPlaylist
}
return 20
return _default.GetMaxNumberOfReferencedSegmentsInPlaylist()
}
func (c *config) GetOfflineContentPath() string {
@ -204,12 +186,29 @@ func (c *config) GetOfflineContentPath() string { @@ -204,12 +186,29 @@ func (c *config) GetOfflineContentPath() string {
}
// This is relative to the webroot, not the project root.
return "static/offline.m4v"
return _default.VideoSettings.OfflineContent
}
func (c *config) GetFFMpegPath() string {
if c.FFMpegPath != "" {
return c.FFMpegPath
}
return _default.FFMpegPath
}
func (c *config) GetVideoStreamQualities() []StreamQuality {
if len(c.VideoSettings.StreamQualities) > 0 {
return c.VideoSettings.StreamQualities
}
return _default.VideoSettings.StreamQualities
}
//Load tries to load the configuration file
func Load(filePath string, versionInfo string) error {
Config = new(config)
_default = getDefaults()
if err := Config.load(filePath); err != nil {
return err
@ -220,8 +219,10 @@ func Load(filePath string, versionInfo string) error { @@ -220,8 +219,10 @@ func Load(filePath string, versionInfo string) error {
// Defaults
// This is relative to the webroot, not the project root.
// Has to be set here instead of pulled from a getter
// since it's serialized to JSON.
if Config.InstanceDetails.ExtraInfoFile == "" {
Config.InstanceDetails.ExtraInfoFile = "/static/content.md"
Config.InstanceDetails.ExtraInfoFile = _default.InstanceDetails.ExtraInfoFile
}
return Config.verifySettings()

40
config/defaults.go

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
package config
import (
"log"
"os/exec"
"strings"
)
func getDefaults() config {
defaults := config{}
defaults.WebServerPort = 8080
defaults.FFMpegPath = getDefaultFFMpegPath()
defaults.VideoSettings.ChunkLengthInSeconds = 4
defaults.Files.MaxNumberInPlaylist = 5
defaults.PublicHLSPath = "webroot/hls"
defaults.PrivateHLSPath = "hls"
defaults.VideoSettings.OfflineContent = "static/offline.m4v"
defaults.InstanceDetails.ExtraInfoFile = "/static/content.md"
defaultQuality := StreamQuality{
IsAudioPassthrough: true,
VideoBitrate: 1200,
EncoderPreset: "veryfast",
}
defaults.VideoSettings.StreamQualities = []StreamQuality{defaultQuality}
return defaults
}
func getDefaultFFMpegPath() string {
cmd := exec.Command("which", "ffmpeg")
out, err := cmd.CombinedOutput()
if err != nil {
log.Panicln("Unable to determine path to ffmpeg. Please specify it in the config file.")
}
path := strings.TrimSpace(string(out))
return path
}

1
controllers/status.go

@ -13,6 +13,7 @@ func GetStatus(w http.ResponseWriter, r *http.Request) { @@ -13,6 +13,7 @@ func GetStatus(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(&w)
status := core.GetStatus()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(status)
}

9
core/chat/chat.go

@ -19,15 +19,6 @@ func Setup(listener models.ChatListener) { @@ -19,15 +19,6 @@ func Setup(listener models.ChatListener) {
pingCh := make(chan models.PingMessage)
doneCh := make(chan bool)
errCh := make(chan error)
// Demo messages only. Remove me eventually!!!
messages = append(messages, models.ChatMessage{"", "Tom Nook", "I'll be there with Bells on! Ho ho!", "https://gamepedia.cursecdn.com/animalcrossingpocketcamp_gamepedia_en/thumb/4/4f/Timmy_Icon.png/120px-Timmy_Icon.png?version=87b38d7d6130411d113486c2db151385", "demo-message-1", "CHAT", true, time.Now()})
messages = append(messages, models.ChatMessage{"", "Redd", "Fool me once, shame on you. Fool me twice, stop foolin' me.", "https://vignette.wikia.nocookie.net/animalcrossing/images/3/3d/Redd2.gif/revision/latest?cb=20100710004252", "demo-message-2", "CHAT", true, time.Now()})
messages = append(messages, models.ChatMessage{"", "Kevin", "You just caught me before I was about to go work out weeweewee!", "https://vignette.wikia.nocookie.net/animalcrossing/images/2/20/NH-Kevin_poster.png/revision/latest/scale-to-width-down/100?cb=20200410185817", "demo-message-3", "CHAT", true, time.Now()})
messages = append(messages, models.ChatMessage{"", "Isabelle", " Isabelle is the mayor's highly capable secretary. She can be forgetful sometimes, but you can always count on her for information about the town. She wears her hair up in a bun that makes her look like a shih tzu. Mostly because she is one! She also has a twin brother named Digby.", "https://dodo.ac/np/images/thumb/7/7b/IsabelleTrophyWiiU.png/200px-IsabelleTrophyWiiU.png", "demo-message-4", "CHAT", true, time.Now()})
messages = append(messages, models.ChatMessage{"", "Judy", "myohmy, I'm dancing my dreams away.", "https://vignette.wikia.nocookie.net/animalcrossing/images/5/50/NH-Judy_poster.png/revision/latest/scale-to-width-down/100?cb=20200522063219", "demo-message-5", "CHAT", true, time.Now()})
messages = append(messages, models.ChatMessage{"", "Blathers", "Blathers is an owl with brown feathers. His face is white and he has a yellow beak. His arms are wing shaped and he has yellow talons. His eyes are very big with small black irises. He also has big pink cheek circles on his cheeks. His belly appears to be checkered in diamonds with light brown and white squares, similar to an argyle vest, which is traditionally associated with academia. His green bowtie further alludes to his academic nature.", "https://vignette.wikia.nocookie.net/animalcrossing/images/b/b3/NH-character-Blathers.png/revision/latest?cb=20200229053519", "demo-message-6", "CHAT", true, time.Now()})
messages = append(messages, getChatHistory()...)
_server = &server{

1
core/chat/client.go

@ -111,6 +111,7 @@ func (c *Client) listenRead() { @@ -111,6 +111,7 @@ func (c *Client) listenRead() {
msg.ID = id
msg.MessageType = "CHAT"
msg.Timestamp = time.Now()
msg.Visible = true
if err := websocket.JSON.Receive(c.ws, &msg); err == io.EOF {
c.doneCh <- true

20
core/chat/server.go

@ -8,6 +8,7 @@ import ( @@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus"
"golang.org/x/net/websocket"
"github.com/gabek/owncast/config"
"github.com/gabek/owncast/models"
)
@ -56,12 +57,6 @@ func (s *server) err(err error) { @@ -56,12 +57,6 @@ func (s *server) err(err error) {
s.errCh <- err
}
func (s *server) sendPastMessages(c *Client) {
for _, msg := range s.Messages {
c.Write(msg)
}
}
func (s *server) sendAll(msg models.ChatMessage) {
for _, c := range s.Clients {
c.Write(msg)
@ -104,7 +99,7 @@ func (s *server) Listen() { @@ -104,7 +99,7 @@ func (s *server) Listen() {
s.Clients[c.id] = c
s.listener.ClientAdded(c.id)
s.sendPastMessages(c)
s.sendWelcomeMessageToClient(c)
// remove a client
case c := <-s.delCh:
@ -128,3 +123,14 @@ func (s *server) Listen() { @@ -128,3 +123,14 @@ func (s *server) Listen() {
}
}
}
func (s *server) sendWelcomeMessageToClient(c *Client) {
go func() {
time.Sleep(5 * time.Second)
initialChatMessageText := fmt.Sprintf("Welcome to %s! %s", config.Config.InstanceDetails.Title, config.Config.InstanceDetails.Summary)
initialMessage := models.ChatMessage{"owncast-server", config.Config.InstanceDetails.Name, initialChatMessageText, config.Config.InstanceDetails.Logo["small"], "initial-message-1", "CHAT", true, time.Now()}
c.Write(initialMessage)
}()
}

3
doc/config-example-full.yaml

@ -55,7 +55,8 @@ videoSettings: @@ -55,7 +55,8 @@ videoSettings:
audioPassthrough: true
# The slower the preset the higher quality the video is.
# Select a preset from https://trac.ffmpeg.org/wiki/Encode/H.264
encoderPreset: superfast
# "superfast" and "ultrafast" are generally not recommended since they look bad.
encoderPreset: veryfast
- medium:
videoBitrate: 800

38
webroot/index.html

@ -16,7 +16,7 @@ @@ -16,7 +16,7 @@
<body class="bg-gray-300 text-gray-800">
<div id="app-container" class="flex no-chat">
<div id="app-container" class="flex chat">
<div id="top-content">
<header class="flex border-b border-gray-900 border-solid shadow-md">
<h1 v-cloak class="flex text-gray-400">
@ -84,7 +84,7 @@ @@ -84,7 +84,7 @@
</section>
</main>
<section id="user-content" v-if="layout === 'desktop'" aria-label="User information">
<section id="user-content" aria-label="User information">
<user-details
v-bind:logo="logo"
v-bind:platforms="socialHandles"
@ -96,13 +96,13 @@ @@ -96,13 +96,13 @@
</section>
<owncast-footer v-if="layout === 'desktop'" v-bind:app-version="appVersion"></owncast-footer>
<owncast-footer v-bind:app-version="appVersion"></owncast-footer>
</div>
<section id="chat-container-wrap" class="flex">
<div v-if="layout !== 'desktop'" id="user-content-touch">
<!-- <div v-if="layout !== 'desktop'" id="user-content-touch">
<user-details
v-bind:logo="logo"
v-bind:platforms="socialHandles"
@ -114,7 +114,7 @@ @@ -114,7 +114,7 @@
<owncast-footer v-bind:app-version="appVersion"></owncast-footer>
</div>
</div> -->
<div id="chat-container" class="bg-gray-800">
<div id="messages-container">
@ -165,7 +165,6 @@ @@ -165,7 +165,6 @@
</div>
<script src="js/usercolors.js"></script>
<script src="js/utils.js"></script>
<script src="js/message.js"></script>
@ -179,5 +178,32 @@ @@ -179,5 +178,32 @@
app.init();
})();
</script>
<noscript>
<style>
[v-cloak] { display: none; }
.noscript {
text-align: center;
padding: 30px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.noscript a {
display: inline;
color: blue;
text-decoration: underline;
}
</style>
<div class="noscript">
<img src="https://github.com/gabek/owncast/raw/master/doc/logo.png">
<br/>
<p>
This <a href="https://github.com/gabek/owncast" target="_blank">Owncast</a> stream requires Javascript to play.
</p>
</div>
</noscript>
</body>
</html>

29
webroot/js/app.js

@ -46,7 +46,6 @@ class Owncast { @@ -46,7 +46,6 @@ class Owncast {
el: '#app-container',
data: {
isOnline: false,
layout: hasTouchScreen() ? 'touch' : 'desktop',
messages: [],
overallMaxViewerCount: 0,
sessionMaxViewerCount: 0,
@ -86,6 +85,8 @@ class Owncast { @@ -86,6 +85,8 @@ class Owncast {
onError: this.handlePlayerError,
});
this.player.init();
this.getChatHistory();
};
setConfigData(data) {
@ -132,16 +133,20 @@ class Owncast { @@ -132,16 +133,20 @@ class Owncast {
return;
}
const message = new Message(model);
this.addMessage(message);
};
this.websocket = ws;
this.messagingInterface.setWebsocket(this.websocket);
};
addMessage(message) {
const existing = this.vueApp.messages.filter(function (item) {
return item.id === message.id;
})
if (existing.length === 0 || !existing) {
this.vueApp.messages = [...this.vueApp.messages, message];
}
};
this.websocket = ws;
this.messagingInterface.setWebsocket(this.websocket);
};
}
// fetch /config data
getConfig() {
@ -296,4 +301,18 @@ class Owncast { @@ -296,4 +301,18 @@ class Owncast {
this.handleOfflineMode();
// stop timers?
};
async getChatHistory() {
const url = "/chat";
const response = await fetch(url);
const data = await response.json();
const messages = data.map(function (message) {
return new Message(message);
})
this.setChatHistory(messages);
}
setChatHistory(messages) {
this.vueApp.messages = messages.concat(this.vueApp.messages);
}
};

43
webroot/js/message.js

@ -74,13 +74,11 @@ class MessagingInterface { @@ -74,13 +74,11 @@ class MessagingInterface {
this.initLocalStates();
if (hasTouchScreen()) {
this.scrollableMessagesContainer = document.body;
setVHvar();
window.addEventListener("orientationchange", setVHvar);
this.tagAppContainer.classList.add('touch-screen');
window.onorientationchange = this.handleOrientationChange.bind(this);
this.handleOrientationChange();
} else {
this.tagAppContainer.classList.add('desktop');
}
}
setWebsocket(socket) {
@ -93,7 +91,7 @@ class MessagingInterface { @@ -93,7 +91,7 @@ class MessagingInterface {
getLocalStorage(KEY_AVATAR) || generateAvatar(`${this.username}${Date.now()}`);
this.updateUsernameFields(this.username);
this.chatDisplayed = getLocalStorage(KEY_CHAT_DISPLAYED) || false;
this.chatDisplayed = getLocalStorage(KEY_CHAT_DISPLAYED) || true;
this.displayChat();
}
@ -112,22 +110,9 @@ class MessagingInterface { @@ -112,22 +110,9 @@ class MessagingInterface {
this.tagAppContainer.classList.add('no-chat');
this.tagAppContainer.classList.remove('chat');
}
this.setChatPlaceholderText();
}
handleOrientationChange() {
var isPortrait = Math.abs(window.orientation % 180) === 0;
if(!isPortrait) {
if (document.body.clientWidth < 1024) {
this.tagAppContainer.classList.add('no-chat');
this.tagAppContainer.classList.add('landscape');
}
} else {
if (this.chatDisplayed) {
this.tagAppContainer.classList.remove('no-chat');
}
this.tagAppContainer.classList.remove('landscape');
}
}
handleChatToggle() {
this.chatDisplayed = !this.chatDisplayed;
@ -241,6 +226,12 @@ class MessagingInterface { @@ -241,6 +226,12 @@ class MessagingInterface {
// clear out things.
this.formMessageInput.value = '';
this.tagMessageFormWarning.innerText = '';
const hasSentFirstChatMessage = getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT);
if (!hasSentFirstChatMessage) {
setLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT, true);
this.setChatPlaceholderText();
}
}
disableChat() {
@ -248,14 +239,22 @@ class MessagingInterface { @@ -248,14 +239,22 @@ class MessagingInterface {
this.formMessageInput.disabled = true;
this.formMessageInput.placeholder = "Chat is offline."
}
// also show "disabled" text/message somewhere.
}
enableChat() {
if (this.formMessageInput) {
this.formMessageInput.disabled = false;
this.formMessageInput.placeholder = "Message"
this.setChatPlaceholderText();
}
}
setChatPlaceholderText() {
const firstMessageChatPlacholderText = "Type here to chat, no account necessary.";
const chatPlaceholderText = "Message"
const hasSentFirstChatMessage = getLocalStorage(KEY_CHAT_FIRST_MESSAGE_SENT);
this.formMessageInput.placeholder = hasSentFirstChatMessage ? chatPlaceholderText : firstMessageChatPlacholderText
}
// handle Vue.js message display
onReceivedMessages(newMessages, oldMessages) {
if (newMessages.length !== oldMessages.length) {

11
webroot/js/utils.js

@ -32,6 +32,9 @@ const VIDEO_SRC = { @@ -32,6 +32,9 @@ const VIDEO_SRC = {
const VIDEO_OPTIONS = {
autoplay: false,
liveui: true, // try this
liveTracker: {
trackingThreshold: 0,
},
sources: [VIDEO_SRC],
};
@ -39,6 +42,7 @@ const VIDEO_OPTIONS = { @@ -39,6 +42,7 @@ const VIDEO_OPTIONS = {
const KEY_USERNAME = 'owncast_username';
const KEY_AVATAR = 'owncast_avatar';
const KEY_CHAT_DISPLAYED = 'owncast_chat';
const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent';
const TIMER_STATUS_UPDATE = 5000; // ms
const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
@ -155,3 +159,10 @@ function secondsToHMMSS(seconds = 0) { @@ -155,3 +159,10 @@ function secondsToHMMSS(seconds = 0) {
return hoursString + minString + secsString;
}
function setVHvar() {
var vh = window.innerHeight * 0.01;
// Then we set the value in the --vh custom property to the root of the document
document.documentElement.style.setProperty('--vh', `${vh}px`);
console.log("== new vh", vh)
}

172
webroot/styles/layout.css

@ -11,10 +11,10 @@ body { @@ -11,10 +11,10 @@ body {
font-size: 14px;
}
a:hover {
text-decoration: underline;
}
/* vuejs attribute to hide things before content ready */
[v-cloak] { visibility: hidden; }
::-webkit-scrollbar {
@ -323,9 +323,11 @@ h2 { @@ -323,9 +323,11 @@ h2 {
#video {
transition: opacity .5s;
opacity: 0;
pointer-events: none;
}
.online #video {
opacity: 1;
pointer-events: auto;
}
@ -364,6 +366,9 @@ h2 { @@ -364,6 +366,9 @@ h2 {
flex-direction: column;
justify-content: flex-end;
}
.touch-screen #chat-container {
height: calc(100vh - var(--header-height) - 3vh);
}
#messages-container {
@ -426,83 +431,6 @@ h2 { @@ -426,83 +431,6 @@ h2 {
/* ************************************************8 */
.landscape #chat-toggle {
display: none;
}
/* ************************************************8 */
/* ************************************************8 */
.touch-screen header {
position: relative;
}
.touch-screen #top-content {
position: fixed;
left: 0;
top: 0;
width: 100%;
z-index: 10;
}
.touch-screen .user-content {
flex-direction: column;
align-content: center;
}
.touch-screen .user-image {
margin: auto;
}
.touch-screen #stream-info {
height: 2.5em;
overflow: hidden;
}
.touch-screen #chat-container-wrap {
display: flex;
align-items: flex-end;
width: 100%;
height: auto;
flex-direction: column;
margin-top: calc(var(--header-height) + var(--video-container-height) + 2.5em);
}
.touch-screen #chat-container {
height: auto;
position: relative;
right: unset;
top: unset;
width: 100%;
z-index: 1;
}
.touch-screen.chat #video-container,
.touch-screen.chat #stream-info,
.touch-screen.chat #user-content {
width: 100%;
}
.touch-screen #video-container {
margin-top: 0;
}
.touch-screen .owncast-video-container {
height: 100%;
}
.touch-screen #user-content-touch {
display: none;
}
.touch-screen #chat-container {
display: block;
}
.touch-screen.no-chat #user-content-touch {
display: block;
}
.touch-screen.no-chat #chat-container {
display: none;
}
/* ************************************************8 */
@media screen and (max-width: 860px) {
:root {
@ -516,33 +444,12 @@ h2 { @@ -516,33 +444,12 @@ h2 {
}
/* single col layout */
@media screen and (max-width: 640px ) {
:root {
--video-container-height: 36vh;
}
.desktop {
--video-container-height: 50vh;
}
.desktop #chat-container {
height: auto;
position: relative;
right: unset;
top: unset;
width: 100%;
z-index: 1;
}
.desktop.chat #video-container,
.desktop.chat #stream-info,
.desktop.chat #user-content {
width: 100%;
--right-col-width: 0;
--video-container-height: 40vh;
}
.desktop #footer,
.desktop.chat #user-content {
display: none;
}
#logo-container {
display: none;
}
@ -552,29 +459,60 @@ h2 { @@ -552,29 +459,60 @@ h2 {
#user-options-container {
max-width: 41%;
}
}
@media screen and (orientation: landscape) and (min-width: 1024px) {
:root {
--video-container-height: 65vh;
#chat-container {
width: 100%;
position: static;
/* min-height: calc(100vh - var(--header-height)); */
height: calc(100vh - var(--header-height) - var(--video-container-height) - 3vh)
}
}
@media screen and (orientation: landscape) and (max-width: 1024px) {
:root .landscape {
--video-container-height: 75vh;
#messages-container {
min-height: unset;
}
.touch-screen.landscape #chat-container-wrap {
margin-top: calc(var(--header-height) + var(--video-container-height));
#user-content {
width: 100%;
}
.touch-screen.landscape .user-content {
display: block;
#stream-info {
width: 100%;
}
#video-container {
width: 100%;
}
.chat #video-container {
width: 100%;
}
.touch-screen.landscape #chat-container {
.chat #user-content {
display: none;
}
.touch-screen.landscape #chat-toggle {
.chat footer {
display: none;
}
}
/* try not making the video fixed position for now */
@media (min-height: 861px) {
/* main {
position: fixed;
z-index: 9;
width: 100%;
}
#user-content {
margin-top: calc(var(--video-container-height) + var(--header-height) + 2em)
} */
}
@media screen and (max-height: 860px ) {
:root {
--video-container-height: 40vh;
}
.user-content {
flex-direction: column;
}
}

Loading…
Cancel
Save