diff --git a/webroot/img/social-icons.gif b/webroot/img/platform-logos.gif
similarity index 100%
rename from webroot/img/social-icons.gif
rename to webroot/img/platform-logos.gif
diff --git a/webroot/js/app-video-only.js b/webroot/js/app-video-only.js
index c64e40a38..bba941590 100644
--- a/webroot/js/app-video-only.js
+++ b/webroot/js/app-video-only.js
@@ -223,7 +223,6 @@ export default class VideoOnly extends Component {
} = state;
const { logo = TEMP_IMAGE } = configData;
- const streamInfoClass = streamOnline ? 'online' : ''; // need?
const mainClass = playerActive ? 'online' : '';
@@ -250,10 +249,10 @@ export default class VideoOnly extends Component {
${streamStatusMessage}
- ${viewerCount} ${pluralize('viewer', viewerCount)}.
+ ${viewerCount} ${pluralize('viewer', viewerCount)}.
`);
diff --git a/webroot/js/app.js b/webroot/js/app.js
index a29b0d3ad..8f9ea0d0f 100644
--- a/webroot/js/app.js
+++ b/webroot/js/app.js
@@ -3,7 +3,7 @@ import htm from '/js/web_modules/htm.js';
const html = htm.bind(h);
import { OwncastPlayer } from './components/player.js';
-import SocialIconsList from './components/social-icons-list.js';
+import SocialIconsList from './components/platform-logos-list.js';
import UsernameForm from './components/chat/username.js';
import VideoPoster from './components/video-poster.js';
import Chat from './components/chat/chat.js';
@@ -44,6 +44,7 @@ export default class App extends Component {
const chatStorage = getLocalStorage(KEY_CHAT_DISPLAYED);
this.hasTouchScreen = hasTouchScreen();
+ this.windowBlurred = false;
this.state = {
websocket: new Websocket(),
@@ -81,6 +82,8 @@ export default class App extends Component {
this.handleUsernameChange = this.handleUsernameChange.bind(this);
this.handleFormFocus = this.handleFormFocus.bind(this);
this.handleFormBlur = this.handleFormBlur.bind(this);
+ this.handleWindowBlur = this.handleWindowBlur.bind(this);
+ this.handleWindowFocus = this.handleWindowFocus.bind(this);
this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 250);
this.handleOfflineMode = this.handleOfflineMode.bind(this);
@@ -102,6 +105,8 @@ export default class App extends Component {
componentDidMount() {
this.getConfig();
window.addEventListener('resize', this.handleWindowResize);
+ window.addEventListener('blur', this.handleWindowBlur);
+ window.addEventListener('focus', this.handleWindowFocus);
if (this.hasTouchScreen) {
window.addEventListener('orientationchange', this.handleWindowResize);
}
@@ -123,6 +128,8 @@ export default class App extends Component {
clearTimeout(this.disableChatTimer);
clearInterval(this.streamDurationTimer);
window.removeEventListener('resize', this.handleWindowResize);
+ window.removeEventListener('blur', this.handleWindowBlur);
+ window.removeEventListener('focus', this.handleWindowFocus);
if (this.hasTouchScreen) {
window.removeEventListener('orientationchange', this.handleWindowResize);
}
@@ -248,6 +255,10 @@ export default class App extends Component {
if (this.player.vjsPlayer && this.player.vjsPlayer.paused()) {
this.handlePlayerEnded();
}
+
+ if (this.windowBlurred) {
+ document.title = ` 🔴 ${this.state.configData && this.state.configData.title}`;
+ }
}
// play video!
@@ -267,6 +278,10 @@ export default class App extends Component {
chatInputEnabled: true,
streamStatusMessage: MESSAGE_ONLINE,
});
+
+ if (this.windowBlurred) {
+ document.title = ` 🟢 ${this.state.configData && this.state.configData.title}`;
+ }
}
setCurrentStreamDuration() {
@@ -335,6 +350,15 @@ export default class App extends Component {
});
}
+ handleWindowBlur() {
+ this.windowBlurred = true;
+ }
+
+ handleWindowFocus() {
+ this.windowBlurred = false;
+ window.document.title = this.state.configData && this.state.configData.title;
+ }
+
render(props, state) {
const {
chatInputEnabled,
@@ -381,7 +405,6 @@ export default class App extends Component {
: null;
const mainClass = playerActive ? 'online' : '';
- const streamInfoClass = streamOnline ? 'online' : ''; // need?
const isPortrait = this.hasTouchScreen && orientation === ORIENTATION_PORTRAIT;
const shortHeight = windowHeight <= HEIGHT_SHORT_WIDE && !isPortrait;
const singleColMode = windowWidth <= WIDTH_SINGLE_COL && !shortHeight;
@@ -462,10 +485,10 @@ export default class App extends Component {
${streamStatusMessage}
- ${viewerCount} ${pluralize('viewer', viewerCount)}.
+ ${viewerCount} ${pluralize('viewer', viewerCount)}.
@@ -514,6 +537,7 @@ export default class App extends Component {
websocket=${websocket}
username=${username}
chatInputEnabled=${chatInputEnabled}
+ instanceTitle=${title}
/>
`;
diff --git a/webroot/js/components/chat/chat.js b/webroot/js/components/chat/chat.js
index eae3fc7cd..a4d5ea6b7 100644
--- a/webroot/js/components/chat/chat.js
+++ b/webroot/js/components/chat/chat.js
@@ -16,10 +16,10 @@ export default class Chat extends Component {
super(props, context);
this.state = {
- webSocketConnected: true,
- messages: [],
chatUserNames: [],
+ messages: [],
newMessagesReceived: false,
+ webSocketConnected: true,
};
this.scrollableMessagesContainer = createRef();
@@ -27,16 +27,20 @@ export default class Chat extends Component {
this.websocket = null;
this.receivedFirstMessages = false;
+ this.windowBlurred = false;
+ this.numMessagesSinceBlur = 0;
+
this.getChatHistory = this.getChatHistory.bind(this);
+ this.handleNetworkingError = this.handleNetworkingError.bind(this);
+ this.handleWindowBlur = this.handleWindowBlur.bind(this);
+ this.handleWindowFocus = this.handleWindowFocus.bind(this);
+ this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 500);
+ this.messageListCallback = this.messageListCallback.bind(this);
this.receivedWebsocketMessage = this.receivedWebsocketMessage.bind(this);
+ this.scrollToBottom = this.scrollToBottom.bind(this);
+ this.submitChat = this.submitChat.bind(this);
this.websocketConnected = this.websocketConnected.bind(this);
this.websocketDisconnected = this.websocketDisconnected.bind(this);
- this.submitChat = this.submitChat.bind(this);
- this.submitChat = this.submitChat.bind(this);
- this.scrollToBottom = this.scrollToBottom.bind(this);
- this.handleWindowResize = debounce(this.handleWindowResize.bind(this), 500);
- this.handleNetworkingError = this.handleNetworkingError.bind(this);
- this.messageListCallback = this.messageListCallback.bind(this);
}
componentDidMount() {
@@ -45,6 +49,11 @@ export default class Chat extends Component {
window.addEventListener('resize', this.handleWindowResize);
+ if (!this.props.messagesOnly) {
+ window.addEventListener('blur', this.handleWindowBlur);
+ window.addEventListener('focus', this.handleWindowFocus);
+ }
+
this.messageListObserver = new MutationObserver(this.messageListCallback);
this.messageListObserver.observe(this.scrollableMessagesContainer.current, { childList: true });
}
@@ -87,6 +96,10 @@ export default class Chat extends Component {
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleWindowResize);
+ if (!this.props.messagesOnly) {
+ window.removeEventListener('blur', this.handleWindowBlur);
+ window.removeEventListener('focus', this.handleWindowFocus);
+ }
this.messageListObserver.disconnect();
}
@@ -141,6 +154,7 @@ export default class Chat extends Component {
addMessage(message) {
const { messages: curMessages } = this.state;
+ const { messagesOnly } = this.props;
// if incoming message has same id as existing message, don't add it
const existing = curMessages.filter(function (item) {
@@ -157,6 +171,11 @@ export default class Chat extends Component {
}
this.setState(newState);
}
+
+ // if window is blurred and we get a new message, add 1 to title
+ if (!messagesOnly && message.type === 'CHAT' && this.windowBlurred) {
+ this.numMessagesSinceBlur += 1;
+ }
}
websocketConnected() {
@@ -216,6 +235,16 @@ export default class Chat extends Component {
this.scrollToBottom();
}
+ handleWindowBlur() {
+ this.windowBlurred = true;
+ }
+
+ handleWindowFocus() {
+ this.windowBlurred = false;
+ this.numMessagesSinceBlur = 0;
+ window.document.title = this.props.instanceTitle;
+ }
+
// if the messages list grows in number of child message nodes due to new messages received, scroll to bottom.
messageListCallback(mutations) {
const numMutations = mutations.length;
@@ -234,9 +263,18 @@ export default class Chat extends Component {
});
}
}
+ // update document title if window blurred
+ if (this.numMessagesSinceBlur && !this.props.messagesOnly && this.windowBlurred) {
+ this.updateDocumentTitle();
+ }
}
};
+ updateDocumentTitle() {
+ const num = this.numMessagesSinceBlur > 10 ? '10+' : this.numMessagesSinceBlur;
+ window.document.title = `${num} 💬 :: ${this.props.instanceTitle}`;
+ }
+
render(props, state) {
const { username, messagesOnly, chatInputEnabled } = props;
const { messages, chatUserNames, webSocketConnected } = state;
diff --git a/webroot/js/components/social-icons-list.js b/webroot/js/components/platform-logos-list.js
similarity index 96%
rename from webroot/js/components/social-icons-list.js
rename to webroot/js/components/platform-logos-list.js
index 55156dea8..efb41172a 100644
--- a/webroot/js/components/social-icons-list.js
+++ b/webroot/js/components/platform-logos-list.js
@@ -2,7 +2,7 @@ import { h, Component } from '/js/web_modules/preact.js';
import htm from '/js/web_modules/htm.js';
const html = htm.bind(h);
-import { SOCIAL_PLATFORMS } from '../utils/social.js';
+import { SOCIAL_PLATFORMS } from '../utils/platforms.js';
import { classNames } from '../utils/helpers.js';
function SocialIcon(props) {
@@ -60,4 +60,4 @@ export default function (props) {
Follow me:
${list}
`;
-}
+}
diff --git a/webroot/js/utils/social.js b/webroot/js/utils/platforms.js
similarity index 94%
rename from webroot/js/utils/social.js
rename to webroot/js/utils/platforms.js
index 9f42063e5..7bcf0be18 100644
--- a/webroot/js/utils/social.js
+++ b/webroot/js/utils/platforms.js
@@ -1,4 +1,4 @@
-// x, y pixel psitions of /img/social.gif image.
+// x, y pixel positions of /img/platform-logos.gif image.
export const SOCIAL_PLATFORMS = {
default: {
name: "default",
diff --git a/webroot/styles/app.css b/webroot/styles/app.css
index b5e23888e..65edae579 100644
--- a/webroot/styles/app.css
+++ b/webroot/styles/app.css
@@ -75,6 +75,12 @@ header {
#stream-info span {
font-size: .70rem;
}
+#stream-viewer-count {
+ display: none;
+}
+.online #stream-viewer-count {
+ display: inline;
+}
/* ************************************************ */
diff --git a/webroot/styles/user-content.css b/webroot/styles/user-content.css
index 148f14b0a..ff396d030 100644
--- a/webroot/styles/user-content.css
+++ b/webroot/styles/user-content.css
@@ -11,7 +11,7 @@
--icon-width: 40px;
height: var(--icon-width);
width: var(--icon-width);
- background-image: url(/img/social-icons.gif);
+ background-image: url(/img/platform-logos.gif);
background-position: calc(var(--imgCol) * var(--icon-width)) calc(var(--imgRow) * var(--icon-width));
transform: scale(.65);
}