mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
Receiver: Add pausing and resuming showDuration timer
This commit is contained in:
parent
205ec8bf23
commit
c12e1aff58
2 changed files with 147 additions and 119 deletions
|
@ -15,3 +15,55 @@ export function mediaItemFromPlayMessage(message: PlayMessage) {
|
||||||
null, null, message.headers, message.metadata
|
null, null, message.headers, message.metadata
|
||||||
) : new MediaItem("");
|
) : new MediaItem("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Timer {
|
||||||
|
private handle: number;
|
||||||
|
private callback: () => void;
|
||||||
|
private delay: number;
|
||||||
|
private startTime: number;
|
||||||
|
private remainingTime: number;
|
||||||
|
|
||||||
|
constructor(callback: () => void, delay: number, autoStart: boolean = true) {
|
||||||
|
this.handle = null;
|
||||||
|
this.callback = callback;
|
||||||
|
this.delay = delay;
|
||||||
|
|
||||||
|
if (autoStart) {
|
||||||
|
this.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(delay?: number) {
|
||||||
|
this.delay = delay ? delay : this.delay;
|
||||||
|
|
||||||
|
if (this.handle) {
|
||||||
|
window.clearTimeout(this.handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.startTime = Date.now();
|
||||||
|
this.remainingTime = null;
|
||||||
|
this.handle = window.setTimeout(this.callback, this.delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause() {
|
||||||
|
if (this.handle) {
|
||||||
|
window.clearTimeout(this.handle);
|
||||||
|
this.handle = null;
|
||||||
|
this.remainingTime = this.delay - (Date.now() - this.startTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public resume() {
|
||||||
|
if (this.remainingTime) {
|
||||||
|
this.start(this.remainingTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop() {
|
||||||
|
if (this.handle) {
|
||||||
|
window.clearTimeout(this.handle);
|
||||||
|
this.handle = null;
|
||||||
|
this.remainingTime = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { EventMessage, EventType, GenericMediaMetadata, KeyEvent, MediaItem, Med
|
||||||
import { Player, PlayerType } from './Player';
|
import { Player, PlayerType } from './Player';
|
||||||
import * as connectionMonitor from 'common/ConnectionMonitor';
|
import * as connectionMonitor from 'common/ConnectionMonitor';
|
||||||
import { supportedAudioTypes } from 'common/MimeTypes';
|
import { supportedAudioTypes } from 'common/MimeTypes';
|
||||||
import { mediaItemFromPlayMessage, playMessageFromMediaItem } from 'common/UtilityFrontend';
|
import { mediaItemFromPlayMessage, playMessageFromMediaItem, Timer } from 'common/UtilityFrontend';
|
||||||
import { toast, ToastIcon } from 'common/components/Toast';
|
import { toast, ToastIcon } from 'common/components/Toast';
|
||||||
import {
|
import {
|
||||||
targetPlayerCtrlStateUpdate,
|
targetPlayerCtrlStateUpdate,
|
||||||
|
@ -16,78 +16,6 @@ import {
|
||||||
|
|
||||||
const logger = window.targetAPI.logger;
|
const logger = window.targetAPI.logger;
|
||||||
|
|
||||||
function formatDuration(duration: number) {
|
|
||||||
if (isNaN(duration)) {
|
|
||||||
return '00:00';
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalSeconds = Math.floor(duration);
|
|
||||||
const hours = Math.floor(totalSeconds / 3600);
|
|
||||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
||||||
const seconds = Math.floor(totalSeconds % 60);
|
|
||||||
|
|
||||||
const paddedMinutes = String(minutes).padStart(2, '0');
|
|
||||||
const paddedSeconds = String(seconds).padStart(2, '0');
|
|
||||||
|
|
||||||
if (hours > 0) {
|
|
||||||
return `${hours}:${paddedMinutes}:${paddedSeconds}`;
|
|
||||||
} else {
|
|
||||||
return `${paddedMinutes}:${paddedSeconds}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function sendPlaybackUpdate(updateState: PlaybackState) {
|
|
||||||
const updateMessage = new PlaybackUpdateMessage(Date.now(), updateState, player.getCurrentTime(), player.getDuration(), player.getPlaybackRate());
|
|
||||||
playbackState = updateState;
|
|
||||||
|
|
||||||
if (updateMessage.generationTime > lastPlayerUpdateGenerationTime) {
|
|
||||||
lastPlayerUpdateGenerationTime = updateMessage.generationTime;
|
|
||||||
window.targetAPI.sendPlaybackUpdate(updateMessage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function onPlayerLoad(value: PlayMessage) {
|
|
||||||
playerCtrlStateUpdate(PlayerControlEvent.Load);
|
|
||||||
|
|
||||||
if (player.getAutoplay()) {
|
|
||||||
setIdleScreenVisible(false, false, value);
|
|
||||||
|
|
||||||
// Subtitles break when seeking post stream initialization for the DASH player.
|
|
||||||
// Its currently done on player initialization.
|
|
||||||
if (player.playerType === PlayerType.Hls || player.playerType === PlayerType.Html) {
|
|
||||||
if (value.time) {
|
|
||||||
player.setCurrentTime(value.time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value.speed) {
|
|
||||||
player.setPlaybackRate(value.speed);
|
|
||||||
playerCtrlStateUpdate(PlayerControlEvent.SetPlaybackRate);
|
|
||||||
}
|
|
||||||
if (value.volume !== null && value.volume >= 0) {
|
|
||||||
volumeChangeHandler(value.volume);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Protocol v2 FCast PlayMessage does not contain volume field and could result in the receiver
|
|
||||||
// getting out-of-sync with the sender on 1st playback.
|
|
||||||
volumeChangeHandler(1.0);
|
|
||||||
window.targetAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: 1.0 });
|
|
||||||
}
|
|
||||||
playerCtrlStateUpdate(PlayerControlEvent.VolumeChange);
|
|
||||||
|
|
||||||
playbackState = PlaybackState.Playing;
|
|
||||||
logger.info('Media playback start:', cachedPlayMediaItem);
|
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemStart, cachedPlayMediaItem)));
|
|
||||||
player.play();
|
|
||||||
|
|
||||||
if (isMediaItem && cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
|
|
||||||
showDurationTimeout = window.setTimeout(mediaEndHandler, cachedPlayMediaItem.showDuration * 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setIdleScreenVisible(true, false, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML elements
|
// HTML elements
|
||||||
const idleIcon = document.getElementById('title-icon');
|
const idleIcon = document.getElementById('title-icon');
|
||||||
const loadingSpinner = document.getElementById('loading-spinner');
|
const loadingSpinner = document.getElementById('loading-spinner');
|
||||||
|
@ -145,11 +73,83 @@ let captionsContentHeight = 0;
|
||||||
|
|
||||||
let cachedPlaylist: PlaylistContent = null;
|
let cachedPlaylist: PlaylistContent = null;
|
||||||
let cachedPlayMediaItem: MediaItem = null;
|
let cachedPlayMediaItem: MediaItem = null;
|
||||||
let showDurationTimeout: number = null;
|
|
||||||
let playlistIndex = 0;
|
let playlistIndex = 0;
|
||||||
let isMediaItem = false;
|
let isMediaItem = false;
|
||||||
let playItemCached = false;
|
let playItemCached = false;
|
||||||
let mediaTitleTimeoutHandle = null;
|
|
||||||
|
let uiHideTimer = new Timer(() => {
|
||||||
|
uiVisible = false;
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
let showDurationTimer = new Timer(mediaEndHandler, 0, false);
|
||||||
|
let mediaTitleShowTimer = new Timer(() => {
|
||||||
|
mediaTitle.style.display = 'none';
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
|
function formatDuration(duration: number) {
|
||||||
|
if (isNaN(duration)) {
|
||||||
|
return '00:00';
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalSeconds = Math.floor(duration);
|
||||||
|
const hours = Math.floor(totalSeconds / 3600);
|
||||||
|
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||||
|
const seconds = Math.floor(totalSeconds % 60);
|
||||||
|
|
||||||
|
const paddedMinutes = String(minutes).padStart(2, '0');
|
||||||
|
const paddedSeconds = String(seconds).padStart(2, '0');
|
||||||
|
|
||||||
|
if (hours > 0) {
|
||||||
|
return `${hours}:${paddedMinutes}:${paddedSeconds}`;
|
||||||
|
} else {
|
||||||
|
return `${paddedMinutes}:${paddedSeconds}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendPlaybackUpdate(updateState: PlaybackState) {
|
||||||
|
const updateMessage = new PlaybackUpdateMessage(Date.now(), updateState, player?.getCurrentTime(), player?.getDuration(), player?.getPlaybackRate());
|
||||||
|
playbackState = updateState;
|
||||||
|
|
||||||
|
if (updateMessage.generationTime > lastPlayerUpdateGenerationTime) {
|
||||||
|
lastPlayerUpdateGenerationTime = updateMessage.generationTime;
|
||||||
|
window.targetAPI.sendPlaybackUpdate(updateMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function onPlayerLoad(value: PlayMessage) {
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.Load);
|
||||||
|
|
||||||
|
if (player.getAutoplay()) {
|
||||||
|
// Subtitles break when seeking post stream initialization for the DASH player.
|
||||||
|
// Its currently done on player initialization.
|
||||||
|
if (player.playerType === PlayerType.Hls || player.playerType === PlayerType.Html) {
|
||||||
|
if (value.time) {
|
||||||
|
player.setCurrentTime(value.time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value.speed) {
|
||||||
|
player.setPlaybackRate(value.speed);
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.SetPlaybackRate);
|
||||||
|
}
|
||||||
|
if (value.volume !== null && value.volume >= 0) {
|
||||||
|
volumeChangeHandler(value.volume);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Protocol v2 FCast PlayMessage does not contain volume field and could result in the receiver
|
||||||
|
// getting out-of-sync with the sender on 1st playback.
|
||||||
|
volumeChangeHandler(1.0);
|
||||||
|
window.targetAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: 1.0 });
|
||||||
|
}
|
||||||
|
playerCtrlStateUpdate(PlayerControlEvent.VolumeChange);
|
||||||
|
|
||||||
|
mediaPlayHandler(value);
|
||||||
|
player.play();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setIdleScreenVisible(true, false, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onPlay(_event, value: PlayMessage) {
|
function onPlay(_event, value: PlayMessage) {
|
||||||
if (!playItemCached) {
|
if (!playItemCached) {
|
||||||
|
@ -169,10 +169,11 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
player.destroy();
|
player.destroy();
|
||||||
|
player = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIdleScreenVisible(true, true);
|
setIdleScreenVisible(true, true);
|
||||||
playbackState = PlaybackState.Idle;
|
sendPlaybackUpdate(PlaybackState.Idle);
|
||||||
playerPrevTime = 0;
|
playerPrevTime = 0;
|
||||||
lastPlayerUpdateGenerationTime = 0;
|
lastPlayerUpdateGenerationTime = 0;
|
||||||
isLive = false;
|
isLive = false;
|
||||||
|
@ -185,7 +186,7 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
|
|
||||||
if (value.container === 'application/dash+xml') {
|
if (value.container === 'application/dash+xml') {
|
||||||
// Player event handlers
|
// Player event handlers
|
||||||
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, () => { mediaStartHandler(value); });
|
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, () => { mediaPlayHandler(value); });
|
||||||
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PAUSED, () => { sendPlaybackUpdate(PlaybackState.Paused); playerCtrlStateUpdate(PlayerControlEvent.Pause); });
|
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PAUSED, () => { sendPlaybackUpdate(PlaybackState.Paused); playerCtrlStateUpdate(PlayerControlEvent.Pause); });
|
||||||
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, () => { mediaEndHandler(); });
|
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, () => { mediaEndHandler(); });
|
||||||
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, () => {
|
player.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, () => {
|
||||||
|
@ -298,7 +299,7 @@ function onPlay(_event, value: PlayMessage) {
|
||||||
|
|
||||||
// Player event handlers
|
// Player event handlers
|
||||||
if (player.playerType === PlayerType.Hls || player.playerType === PlayerType.Html) {
|
if (player.playerType === PlayerType.Hls || player.playerType === PlayerType.Html) {
|
||||||
videoElement.onplay = () => { mediaStartHandler(value); };
|
videoElement.onplay = () => { mediaPlayHandler(value); };
|
||||||
videoElement.onpause = () => { sendPlaybackUpdate(PlaybackState.Paused); playerCtrlStateUpdate(PlayerControlEvent.Pause); };
|
videoElement.onpause = () => { sendPlaybackUpdate(PlaybackState.Paused); playerCtrlStateUpdate(PlayerControlEvent.Pause); };
|
||||||
videoElement.onended = () => { mediaEndHandler(); };
|
videoElement.onended = () => { mediaEndHandler(); };
|
||||||
videoElement.ontimeupdate = () => {
|
videoElement.ontimeupdate = () => {
|
||||||
|
@ -384,11 +385,7 @@ function setPlaylistItem(index: number) {
|
||||||
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
cachedPlayMediaItem = cachedPlaylist.items[playlistIndex];
|
||||||
playItemCached = true;
|
playItemCached = true;
|
||||||
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
window.targetAPI.sendPlayRequest(playMessageFromMediaItem(cachedPlaylist.items[playlistIndex]), playlistIndex);
|
||||||
|
showDurationTimer.stop();
|
||||||
if (showDurationTimeout) {
|
|
||||||
window.clearTimeout(showDurationTimeout);
|
|
||||||
showDurationTimeout = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
logger.warn(`Playlist index out of bounds ${index}, ignoring...`);
|
logger.warn(`Playlist index out of bounds ${index}, ignoring...`);
|
||||||
|
@ -491,14 +488,7 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
||||||
captionsContentHeight = mediaTitle.getBoundingClientRect().height - captionsLineHeight;
|
captionsContentHeight = mediaTitle.getBoundingClientRect().height - captionsLineHeight;
|
||||||
const captionsHeight = captionsBaseHeightExpanded + captionsContentHeight;
|
const captionsHeight = captionsBaseHeightExpanded + captionsContentHeight;
|
||||||
mediaTitle.setAttribute("style", `display: block; bottom: ${captionsHeight}px;`);
|
mediaTitle.setAttribute("style", `display: block; bottom: ${captionsHeight}px;`);
|
||||||
|
mediaTitleShowTimer.start();
|
||||||
if (mediaTitleTimeoutHandle) {
|
|
||||||
clearTimeout(mediaTitleTimeoutHandle);
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaTitleTimeoutHandle = setTimeout(() => {
|
|
||||||
mediaTitle.style.display = 'none';
|
|
||||||
}, 5000);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -509,11 +499,12 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
|
||||||
case PlayerControlEvent.Pause:
|
case PlayerControlEvent.Pause:
|
||||||
playerCtrlAction.setAttribute("class", "play iconSize");
|
playerCtrlAction.setAttribute("class", "play iconSize");
|
||||||
stopUiHideTimer();
|
stopUiHideTimer();
|
||||||
|
showDurationTimer.pause();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlayerControlEvent.Play:
|
case PlayerControlEvent.Play:
|
||||||
playerCtrlAction.setAttribute("class", "pause iconSize");
|
playerCtrlAction.setAttribute("class", "pause iconSize");
|
||||||
startUiHideTimer();
|
uiHideTimer.start();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case PlayerControlEvent.VolumeChange: {
|
case PlayerControlEvent.VolumeChange: {
|
||||||
|
@ -825,12 +816,18 @@ function setIdleScreenVisible(visible: boolean, loading: boolean = false, messag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function mediaStartHandler(message: PlayMessage) {
|
function mediaPlayHandler(message: PlayMessage) {
|
||||||
if (playbackState === PlaybackState.Idle) {
|
if (playbackState === PlaybackState.Idle) {
|
||||||
logger.info('Media playback start:', cachedPlayMediaItem);
|
logger.info('Media playback start:', cachedPlayMediaItem);
|
||||||
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemStart, cachedPlayMediaItem)));
|
window.targetAPI.sendEvent(new EventMessage(Date.now(), new MediaItemEvent(EventType.MediaItemStart, cachedPlayMediaItem)));
|
||||||
|
setIdleScreenVisible(false, false, message);
|
||||||
|
|
||||||
setIdleScreenVisible(false, false, message)
|
if (isMediaItem && cachedPlayMediaItem.showDuration && cachedPlayMediaItem.showDuration > 0) {
|
||||||
|
showDurationTimer.start(cachedPlayMediaItem.showDuration * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
showDurationTimer.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
sendPlaybackUpdate(PlaybackState.Playing);
|
sendPlaybackUpdate(PlaybackState.Playing);
|
||||||
|
@ -838,10 +835,7 @@ function mediaStartHandler(message: PlayMessage) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function mediaEndHandler() {
|
function mediaEndHandler() {
|
||||||
if (showDurationTimeout) {
|
showDurationTimer.stop();
|
||||||
window.clearTimeout(showDurationTimeout);
|
|
||||||
showDurationTimeout = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMediaItem) {
|
if (isMediaItem) {
|
||||||
playlistIndex++;
|
playlistIndex++;
|
||||||
|
@ -873,24 +867,10 @@ function mediaEndHandler() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component hiding
|
// Component hiding
|
||||||
let uiHideTimer = null;
|
|
||||||
let uiVisible = true;
|
let uiVisible = true;
|
||||||
|
|
||||||
function startUiHideTimer() {
|
|
||||||
if (uiHideTimer === null) {
|
|
||||||
uiHideTimer = window.setTimeout(() => {
|
|
||||||
uiHideTimer = null;
|
|
||||||
uiVisible = false;
|
|
||||||
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stopUiHideTimer() {
|
function stopUiHideTimer() {
|
||||||
if (uiHideTimer) {
|
uiHideTimer.stop();
|
||||||
window.clearTimeout(uiHideTimer);
|
|
||||||
uiHideTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!uiVisible) {
|
if (!uiVisible) {
|
||||||
uiVisible = true;
|
uiVisible = true;
|
||||||
|
@ -899,11 +879,7 @@ function stopUiHideTimer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
document.onmouseout = () => {
|
document.onmouseout = () => {
|
||||||
if (uiHideTimer) {
|
uiHideTimer.stop();
|
||||||
window.clearTimeout(uiHideTimer);
|
|
||||||
uiHideTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
uiVisible = false;
|
uiVisible = false;
|
||||||
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
|
||||||
}
|
}
|
||||||
|
@ -912,7 +888,7 @@ document.onmousemove = () => {
|
||||||
stopUiHideTimer();
|
stopUiHideTimer();
|
||||||
|
|
||||||
if (player && !player.isPaused()) {
|
if (player && !player.isPaused()) {
|
||||||
startUiHideTimer();
|
uiHideTimer.start();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue