0

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

This commit is contained in:
Ginger Wong 2020-07-19 15:17:03 -07:00
commit a3613612eb
12 changed files with 239 additions and 205 deletions

View File

@ -3,8 +3,6 @@ package config
import ( import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"os/exec"
"strings"
"github.com/gabek/owncast/utils" "github.com/gabek/owncast/utils"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -13,6 +11,7 @@ import (
//Config contains a reference to the configuration //Config contains a reference to the configuration
var Config *config var Config *config
var _default config
type config struct { type config struct {
ChatDatabaseFilePath string `yaml:"chatDatabaseFile"` ChatDatabaseFilePath string `yaml:"chatDatabaseFile"`
@ -116,6 +115,10 @@ func (c *config) load(filePath string) error {
} }
func (c *config) verifySettings() 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 { if c.S3.Enabled && c.IPFS.Enabled {
return errors.New("s3 and IPFS support cannot be enabled at the same time; choose one") 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 {
return nil 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 { func (c *config) GetVideoSegmentSecondsLength() int {
if c.VideoSettings.ChunkLengthInSeconds != 0 { if c.VideoSettings.ChunkLengthInSeconds != 0 {
return c.VideoSettings.ChunkLengthInSeconds return c.VideoSettings.ChunkLengthInSeconds
} }
// Default return _default.GetVideoSegmentSecondsLength()
return 4
} }
func (c *config) GetPublicHLSSavePath() string { func (c *config) GetPublicHLSSavePath() string {
@ -170,7 +153,7 @@ func (c *config) GetPublicHLSSavePath() string {
return c.PublicHLSPath return c.PublicHLSPath
} }
return "webroot/hls" return _default.PublicHLSPath
} }
func (c *config) GetPrivateHLSSavePath() string { func (c *config) GetPrivateHLSSavePath() string {
@ -178,7 +161,7 @@ func (c *config) GetPrivateHLSSavePath() string {
return c.PrivateHLSPath return c.PrivateHLSPath
} }
return "hls" return _default.PrivateHLSPath
} }
func (c *config) GetPublicWebServerPort() int { func (c *config) GetPublicWebServerPort() int {
@ -186,8 +169,7 @@ func (c *config) GetPublicWebServerPort() int {
return c.WebServerPort return c.WebServerPort
} }
// Default web server port return _default.WebServerPort
return 8080
} }
func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int { func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int {
@ -195,7 +177,7 @@ func (c *config) GetMaxNumberOfReferencedSegmentsInPlaylist() int {
return c.Files.MaxNumberInPlaylist return c.Files.MaxNumberInPlaylist
} }
return 20 return _default.GetMaxNumberOfReferencedSegmentsInPlaylist()
} }
func (c *config) GetOfflineContentPath() string { func (c *config) GetOfflineContentPath() string {
@ -204,12 +186,29 @@ func (c *config) GetOfflineContentPath() string {
} }
// This is relative to the webroot, not the project root. // 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 //Load tries to load the configuration file
func Load(filePath string, versionInfo string) error { func Load(filePath string, versionInfo string) error {
Config = new(config) Config = new(config)
_default = getDefaults()
if err := Config.load(filePath); err != nil { if err := Config.load(filePath); err != nil {
return err return err
@ -220,8 +219,10 @@ func Load(filePath string, versionInfo string) error {
// Defaults // Defaults
// This is relative to the webroot, not the project root. // 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 == "" { if Config.InstanceDetails.ExtraInfoFile == "" {
Config.InstanceDetails.ExtraInfoFile = "/static/content.md" Config.InstanceDetails.ExtraInfoFile = _default.InstanceDetails.ExtraInfoFile
} }
return Config.verifySettings() return Config.verifySettings()

40
config/defaults.go Normal file
View File

@ -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
}

View File

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

View File

@ -19,15 +19,6 @@ func Setup(listener models.ChatListener) {
pingCh := make(chan models.PingMessage) pingCh := make(chan models.PingMessage)
doneCh := make(chan bool) doneCh := make(chan bool)
errCh := make(chan error) 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()...) messages = append(messages, getChatHistory()...)
_server = &server{ _server = &server{

View File

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

View File

@ -8,6 +8,7 @@ import (
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/net/websocket" "golang.org/x/net/websocket"
"github.com/gabek/owncast/config"
"github.com/gabek/owncast/models" "github.com/gabek/owncast/models"
) )
@ -56,12 +57,6 @@ func (s *server) err(err error) {
s.errCh <- err s.errCh <- err
} }
func (s *server) sendPastMessages(c *Client) {
for _, msg := range s.Messages {
c.Write(msg)
}
}
func (s *server) sendAll(msg models.ChatMessage) { func (s *server) sendAll(msg models.ChatMessage) {
for _, c := range s.Clients { for _, c := range s.Clients {
c.Write(msg) c.Write(msg)
@ -104,7 +99,7 @@ func (s *server) Listen() {
s.Clients[c.id] = c s.Clients[c.id] = c
s.listener.ClientAdded(c.id) s.listener.ClientAdded(c.id)
s.sendPastMessages(c) s.sendWelcomeMessageToClient(c)
// remove a client // remove a client
case c := <-s.delCh: case c := <-s.delCh:
@ -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)
}()
}

View File

@ -55,7 +55,8 @@ videoSettings:
audioPassthrough: true audioPassthrough: true
# The slower the preset the higher quality the video is. # The slower the preset the higher quality the video is.
# Select a preset from https://trac.ffmpeg.org/wiki/Encode/H.264 # 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: - medium:
videoBitrate: 800 videoBitrate: 800

View File

@ -16,7 +16,7 @@
<body class="bg-gray-300 text-gray-800"> <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"> <div id="top-content">
<header class="flex border-b border-gray-900 border-solid shadow-md"> <header class="flex border-b border-gray-900 border-solid shadow-md">
<h1 v-cloak class="flex text-gray-400"> <h1 v-cloak class="flex text-gray-400">
@ -84,7 +84,7 @@
</section> </section>
</main> </main>
<section id="user-content" v-if="layout === 'desktop'" aria-label="User information"> <section id="user-content" aria-label="User information">
<user-details <user-details
v-bind:logo="logo" v-bind:logo="logo"
v-bind:platforms="socialHandles" v-bind:platforms="socialHandles"
@ -96,13 +96,13 @@
</section> </section>
<owncast-footer v-if="layout === 'desktop'" v-bind:app-version="appVersion"></owncast-footer> <owncast-footer v-bind:app-version="appVersion"></owncast-footer>
</div> </div>
<section id="chat-container-wrap" class="flex"> <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 <user-details
v-bind:logo="logo" v-bind:logo="logo"
v-bind:platforms="socialHandles" v-bind:platforms="socialHandles"
@ -114,7 +114,7 @@
<owncast-footer v-bind:app-version="appVersion"></owncast-footer> <owncast-footer v-bind:app-version="appVersion"></owncast-footer>
</div> </div> -->
<div id="chat-container" class="bg-gray-800"> <div id="chat-container" class="bg-gray-800">
<div id="messages-container"> <div id="messages-container">
@ -165,7 +165,6 @@
</div> </div>
<script src="js/usercolors.js"></script> <script src="js/usercolors.js"></script>
<script src="js/utils.js"></script> <script src="js/utils.js"></script>
<script src="js/message.js"></script> <script src="js/message.js"></script>
@ -179,5 +178,32 @@
app.init(); app.init();
})(); })();
</script> </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> </body>
</html> </html>

View File

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

View File

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

View File

@ -32,6 +32,9 @@ const VIDEO_SRC = {
const VIDEO_OPTIONS = { const VIDEO_OPTIONS = {
autoplay: false, autoplay: false,
liveui: true, // try this liveui: true, // try this
liveTracker: {
trackingThreshold: 0,
},
sources: [VIDEO_SRC], sources: [VIDEO_SRC],
}; };
@ -39,6 +42,7 @@ const VIDEO_OPTIONS = {
const KEY_USERNAME = 'owncast_username'; const KEY_USERNAME = 'owncast_username';
const KEY_AVATAR = 'owncast_avatar'; const KEY_AVATAR = 'owncast_avatar';
const KEY_CHAT_DISPLAYED = 'owncast_chat'; const KEY_CHAT_DISPLAYED = 'owncast_chat';
const KEY_CHAT_FIRST_MESSAGE_SENT = 'owncast_first_message_sent';
const TIMER_STATUS_UPDATE = 5000; // ms const TIMER_STATUS_UPDATE = 5000; // ms
const TIMER_WEBSOCKET_RECONNECT = 5000; // ms const TIMER_WEBSOCKET_RECONNECT = 5000; // ms
@ -154,4 +158,11 @@ function secondsToHMMSS(seconds = 0) {
const secsString = secs < 10 ? `0${secs}` : `${secs}`; const secsString = secs < 10 ? `0${secs}` : `${secs}`;
return hoursString + minString + secsString; 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)
}

View File

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