mirror of
				https://github.com/owncast/owncast.git
				synced 2025-11-04 05:17:27 +08:00 
			
		
		
		
	display last online time (#1125)
* - if offline calculate and display last online time to address https://github.com/owncast/owncast/issues/1111 - clean up status bar styles * clean up console
This commit is contained in:
		@ -12,7 +12,7 @@ import { OwncastPlayer } from './components/player.js';
 | 
				
			|||||||
import Websocket from './utils/websocket.js';
 | 
					import Websocket from './utils/websocket.js';
 | 
				
			||||||
const websocket = new Websocket();
 | 
					const websocket = new Websocket();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { addNewlines, pluralize } from './utils/helpers.js';
 | 
					import { addNewlines, makeLastOnlineString, pluralize } from './utils/helpers.js';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  URL_CONFIG,
 | 
					  URL_CONFIG,
 | 
				
			||||||
  URL_STATUS,
 | 
					  URL_STATUS,
 | 
				
			||||||
@ -38,6 +38,7 @@ export default class VideoOnly extends Component {
 | 
				
			|||||||
      //status
 | 
					      //status
 | 
				
			||||||
      streamStatusMessage: MESSAGE_OFFLINE,
 | 
					      streamStatusMessage: MESSAGE_OFFLINE,
 | 
				
			||||||
      viewerCount: '',
 | 
					      viewerCount: '',
 | 
				
			||||||
 | 
					      lastDisconnectTime: null,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // timers
 | 
					    // timers
 | 
				
			||||||
@ -134,9 +135,7 @@ export default class VideoOnly extends Component {
 | 
				
			|||||||
    if (!status) {
 | 
					    if (!status) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const { viewerCount, online } = status;
 | 
					    const { viewerCount, online, lastDisconnectTime } = status;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.lastDisconnectTime = status.lastDisconnectTime;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (status.online && !curStreamOnline) {
 | 
					    if (status.online && !curStreamOnline) {
 | 
				
			||||||
      // stream has just come online.
 | 
					      // stream has just come online.
 | 
				
			||||||
@ -148,6 +147,7 @@ export default class VideoOnly extends Component {
 | 
				
			|||||||
    this.setState({
 | 
					    this.setState({
 | 
				
			||||||
      viewerCount,
 | 
					      viewerCount,
 | 
				
			||||||
      streamOnline: online,
 | 
					      streamOnline: online,
 | 
				
			||||||
 | 
					      lastDisconnectTime,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -215,15 +215,18 @@ export default class VideoOnly extends Component {
 | 
				
			|||||||
      playerActive,
 | 
					      playerActive,
 | 
				
			||||||
      streamOnline,
 | 
					      streamOnline,
 | 
				
			||||||
      streamStatusMessage,
 | 
					      streamStatusMessage,
 | 
				
			||||||
 | 
					      lastDisconnectTime,
 | 
				
			||||||
      isPlaying,
 | 
					      isPlaying,
 | 
				
			||||||
    } = state;
 | 
					    } = state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { logo = TEMP_IMAGE, customStyles } = configData;
 | 
					    const { logo = TEMP_IMAGE, customStyles } = configData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const viewerCountMessage =
 | 
					    let viewerCountMessage = '';
 | 
				
			||||||
      streamOnline && viewerCount > 0
 | 
					    if (streamOnline && viewerCount > 0) {
 | 
				
			||||||
        ? html`${viewerCount} ${pluralize('viewer', viewerCount)}`
 | 
					      viewerCountMessage = html`${viewerCount} ${pluralize('viewer', viewerCount)}`;
 | 
				
			||||||
        : null;
 | 
					    } else if (lastDisconnectTime) {
 | 
				
			||||||
 | 
					      viewerCountMessage = makeLastOnlineString(lastDisconnectTime);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const mainClass = playerActive ? 'online' : '';
 | 
					    const mainClass = playerActive ? 'online' : '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -252,10 +255,10 @@ export default class VideoOnly extends Component {
 | 
				
			|||||||
        <section
 | 
					        <section
 | 
				
			||||||
          id="stream-info"
 | 
					          id="stream-info"
 | 
				
			||||||
          aria-label="Stream status"
 | 
					          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"
 | 
					          class="flex flex-row justify-between font-mono py-2 px-4 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid"
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          <span>${streamStatusMessage}</span>
 | 
					          <span class="text-xs">${streamStatusMessage}</span>
 | 
				
			||||||
          <span id="stream-viewer-count">${viewerCountMessage}</span>
 | 
					          <span id="stream-viewer-count" class="text-xs text-right">${viewerCountMessage}</span>
 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
      </main>
 | 
					      </main>
 | 
				
			||||||
    `;
 | 
					    `;
 | 
				
			||||||
 | 
				
			|||||||
@ -8,11 +8,6 @@ import UsernameForm from './components/chat/username.js';
 | 
				
			|||||||
import VideoPoster from './components/video-poster.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 {
 | 
					 | 
				
			||||||
  parseSecondsToDurationString,
 | 
					 | 
				
			||||||
  hasTouchScreen,
 | 
					 | 
				
			||||||
  getOrientation,
 | 
					 | 
				
			||||||
} from './utils/helpers.js';
 | 
					 | 
				
			||||||
import ExternalActionModal, {
 | 
					import ExternalActionModal, {
 | 
				
			||||||
  ExternalActionButton,
 | 
					  ExternalActionButton,
 | 
				
			||||||
} from './components/external-action-modal.js';
 | 
					} from './components/external-action-modal.js';
 | 
				
			||||||
@ -24,6 +19,10 @@ import {
 | 
				
			|||||||
  debounce,
 | 
					  debounce,
 | 
				
			||||||
  generateUsername,
 | 
					  generateUsername,
 | 
				
			||||||
  getLocalStorage,
 | 
					  getLocalStorage,
 | 
				
			||||||
 | 
					  getOrientation,
 | 
				
			||||||
 | 
					  hasTouchScreen,
 | 
				
			||||||
 | 
					  makeLastOnlineString,
 | 
				
			||||||
 | 
					  parseSecondsToDurationString,
 | 
				
			||||||
  pluralize,
 | 
					  pluralize,
 | 
				
			||||||
  setLocalStorage,
 | 
					  setLocalStorage,
 | 
				
			||||||
} from './utils/helpers.js';
 | 
					} from './utils/helpers.js';
 | 
				
			||||||
@ -72,6 +71,7 @@ export default class App extends Component {
 | 
				
			|||||||
      // status
 | 
					      // status
 | 
				
			||||||
      streamStatusMessage: MESSAGE_OFFLINE,
 | 
					      streamStatusMessage: MESSAGE_OFFLINE,
 | 
				
			||||||
      viewerCount: '',
 | 
					      viewerCount: '',
 | 
				
			||||||
 | 
					      lastDisconnectTime: null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // dom
 | 
					      // dom
 | 
				
			||||||
      windowWidth: window.innerWidth,
 | 
					      windowWidth: window.innerWidth,
 | 
				
			||||||
@ -208,9 +208,7 @@ export default class App extends Component {
 | 
				
			|||||||
    if (!status) {
 | 
					    if (!status) {
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const { viewerCount, online, lastConnectTime, streamTitle } = status;
 | 
					    const { viewerCount, online, lastConnectTime, streamTitle, lastDisconnectTime } = status;
 | 
				
			||||||
 | 
					 | 
				
			||||||
    this.lastDisconnectTime = status.lastDisconnectTime;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (status.online && !curStreamOnline) {
 | 
					    if (status.online && !curStreamOnline) {
 | 
				
			||||||
      // stream has just come online.
 | 
					      // stream has just come online.
 | 
				
			||||||
@ -225,6 +223,7 @@ export default class App extends Component {
 | 
				
			|||||||
      lastConnectTime,
 | 
					      lastConnectTime,
 | 
				
			||||||
      streamOnline: online,
 | 
					      streamOnline: online,
 | 
				
			||||||
      streamTitle,
 | 
					      streamTitle,
 | 
				
			||||||
 | 
					      lastDisconnectTime,
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -260,7 +259,7 @@ export default class App extends Component {
 | 
				
			|||||||
    clearInterval(this.streamDurationTimer);
 | 
					    clearInterval(this.streamDurationTimer);
 | 
				
			||||||
    const remainingChatTime =
 | 
					    const remainingChatTime =
 | 
				
			||||||
      TIMER_DISABLE_CHAT_AFTER_OFFLINE -
 | 
					      TIMER_DISABLE_CHAT_AFTER_OFFLINE -
 | 
				
			||||||
      (Date.now() - new Date(this.lastDisconnectTime));
 | 
					      (Date.now() - new Date(this.state.lastDisconnectTime));
 | 
				
			||||||
    const countdown = remainingChatTime < 0 ? 0 : remainingChatTime;
 | 
					    const countdown = remainingChatTime < 0 ? 0 : remainingChatTime;
 | 
				
			||||||
    this.disableChatTimer = setTimeout(this.disableChatInput, countdown);
 | 
					    this.disableChatTimer = setTimeout(this.disableChatInput, countdown);
 | 
				
			||||||
    this.setState({
 | 
					    this.setState({
 | 
				
			||||||
@ -501,6 +500,7 @@ export default class App extends Component {
 | 
				
			|||||||
      windowHeight,
 | 
					      windowHeight,
 | 
				
			||||||
      windowWidth,
 | 
					      windowWidth,
 | 
				
			||||||
      externalAction,
 | 
					      externalAction,
 | 
				
			||||||
 | 
					      lastDisconnectTime,
 | 
				
			||||||
    } = state;
 | 
					    } = state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
@ -520,10 +520,13 @@ export default class App extends Component {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const tagList = tags !== null && tags.length > 0 && tags.join(' #');
 | 
					    const tagList = tags !== null && tags.length > 0 && tags.join(' #');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const viewerCountMessage =
 | 
					    let viewerCountMessage = '';
 | 
				
			||||||
      streamOnline && viewerCount > 0
 | 
					    if (streamOnline && viewerCount > 0) {
 | 
				
			||||||
        ? html`${viewerCount} ${pluralize('viewer', viewerCount)}`
 | 
					      viewerCountMessage = html`${viewerCount} ${pluralize('viewer', viewerCount)}`;
 | 
				
			||||||
        : null;
 | 
					    } else if (lastDisconnectTime) {
 | 
				
			||||||
 | 
					      viewerCountMessage = makeLastOnlineString(lastDisconnectTime);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const mainClass = playerActive ? 'online' : '';
 | 
					    const mainClass = playerActive ? 'online' : '';
 | 
				
			||||||
    const isPortrait =
 | 
					    const isPortrait =
 | 
				
			||||||
@ -647,10 +650,10 @@ export default class App extends Component {
 | 
				
			|||||||
          <section
 | 
					          <section
 | 
				
			||||||
            id="stream-info"
 | 
					            id="stream-info"
 | 
				
			||||||
            aria-label="Stream status"
 | 
					            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"
 | 
					            class="flex text-center flex-row justify-between font-mono py-2 px-4 bg-gray-900 text-indigo-200 shadow-md border-b border-gray-100 border-solid"
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <span>${streamStatusMessage}</span>
 | 
					            <span class="text-xs">${streamStatusMessage}</span>
 | 
				
			||||||
            <span id="stream-viewer-count">${viewerCountMessage}</span>
 | 
					            <span id="stream-viewer-count" class="text-xs text-right">${viewerCountMessage}</span>
 | 
				
			||||||
          </section>
 | 
					          </section>
 | 
				
			||||||
        </main>
 | 
					        </main>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import {
 | 
				
			|||||||
} from '../../utils/user-colors.js';
 | 
					} from '../../utils/user-colors.js';
 | 
				
			||||||
import { convertToText } from '../../utils/chat.js';
 | 
					import { convertToText } from '../../utils/chat.js';
 | 
				
			||||||
import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
 | 
					import { SOCKET_MESSAGE_TYPES } from '../../utils/websocket.js';
 | 
				
			||||||
 | 
					import { getDiffInDaysFromNow } from '../../utils/helpers.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ChatMessageView extends Component {
 | 
					export default class ChatMessageView extends Component {
 | 
				
			||||||
  constructor(props) {
 | 
					  constructor(props) {
 | 
				
			||||||
@ -155,7 +156,7 @@ function formatTimestamp(sentAt) {
 | 
				
			|||||||
    return '';
 | 
					    return '';
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let diffInDays = (new Date() - sentAt) / (24 * 3600 * 1000);
 | 
					  let diffInDays = getDiffInDaysFromNow(sentAt); //(new Date() - sentAt) / (24 * 3600 * 1000);
 | 
				
			||||||
  if (diffInDays >= 1) {
 | 
					  if (diffInDays >= 1) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      `Sent at ${sentAt.toLocaleDateString('en-US', {
 | 
					      `Sent at ${sentAt.toLocaleDateString('en-US', {
 | 
				
			||||||
 | 
				
			|||||||
@ -156,3 +156,26 @@ export function debounce(fn, time) {
 | 
				
			|||||||
    timeout = setTimeout(functionCall, time);
 | 
					    timeout = setTimeout(functionCall, time);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getDiffInDaysFromNow(timestamp) {
 | 
				
			||||||
 | 
					  const time = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;
 | 
				
			||||||
 | 
					  return (new Date() - time) / (24 * 3600 * 1000);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// "Last live today at [time]" or "last live [date]"
 | 
				
			||||||
 | 
					export function makeLastOnlineString(timestamp) {
 | 
				
			||||||
 | 
					  if (!timestamp) {
 | 
				
			||||||
 | 
					    return '';
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  let string = '';
 | 
				
			||||||
 | 
					  const time = new Date(timestamp);
 | 
				
			||||||
 | 
					  let diffInDays = getDiffInDaysFromNow(time);
 | 
				
			||||||
 | 
					  if (diffInDays > 1) {
 | 
				
			||||||
 | 
					    string = time.toLocaleDateString();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    const atTime = time.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
 | 
				
			||||||
 | 
					    string = `Today ${atTime}`;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return `Last live: ${string}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -78,17 +78,6 @@ header {
 | 
				
			|||||||
  display: none;
 | 
					  display: none;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
#stream-info span {
 | 
					 | 
				
			||||||
  font-size: .70rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
#stream-viewer-count {
 | 
					 | 
				
			||||||
  display: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.online #stream-viewer-count {
 | 
					 | 
				
			||||||
  display: inline;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#external-actions-container {
 | 
					#external-actions-container {
 | 
				
			||||||
  margin: 1em 0;
 | 
					  margin: 1em 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -24,8 +24,3 @@ The styles in this file mostly ovveride those coming from chat.css
 | 
				
			|||||||
  opacity: 1;
 | 
					  opacity: 1;
 | 
				
			||||||
  pointer-events: auto;
 | 
					  pointer-events: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#video-only #stream-info {
 | 
					 | 
				
			||||||
  height: 3rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user