@@ -21,8 +22,9 @@
-
Waiting for a connection
-
+
Waiting for a connection
+
+
@@ -54,6 +56,10 @@
+
App will continue to run as tray app when the window is closed
diff --git a/receivers/electron/src/main/style.css b/receivers/electron/src/main/style.css
index aad6665..95dfbb6 100644
--- a/receivers/electron/src/main/style.css
+++ b/receivers/electron/src/main/style.css
@@ -1,50 +1,3 @@
-body, html {
- height: 100%;
- margin: 0;
-}
-
-#main-container {
- position: relative;
- height: 100%;
- overflow: hidden;
-}
-
-.video {
- height: 100%;
- width: 100%;
- object-fit: cover;
-}
-
-.non-selectable {
- user-select: none;
-}
-
-.card {
- display: flex;
- flex-direction: column;
- text-align: center;
-
- background-color: rgba(20, 20, 20, 0.5);
- padding: 25px;
- border-radius: 10px;
- border: 1px solid #2E2E2E;
- scrollbar-width: thin;
- overflow: auto;
-}
-
-.card-title {
- font-weight: 700;
- line-height: 24px;
- margin: 10px;
-}
-
-.card-title-separator {
- height: 1px;
- background: #2E2E2E;
- margin-top: 3px;
- margin-bottom: 3px;
-}
-
.button {
display: inline-block;
align-items: center;
@@ -83,98 +36,6 @@ body, html {
background: #3E3E3E;
}
-#ui-container {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
-}
-
-#overlay {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- color: white;
- gap: 15vw;
-
- font-family: InterVariable;
- font-size: 20px;
- font-style: normal;
- font-weight: 400;
-}
-
-#title-container {
- display: flex;
- justify-content: center;
- align-items: center;
-}
-
-#title-text {
- font-family: Outfit;
- font-size: 100px;
- font-weight: 800;
- text-align: center;
-
- background-image: linear-gradient(180deg, #FFFFFF 5.9%, #D3D3D3 100%);
- background-clip: text;
- -webkit-text-fill-color: transparent;
-}
-
-#title-icon {
- width: 84px;
- height: 84px;
-
- background-image: url(../../assets/icons/app/icon.svg);
- background-size: cover;
- margin-right: 15px;
-}
-
-#connection-status {
- padding: 25px;
- text-align: center;
-}
-
-#main-view {
- padding: 25px;
-}
-
-#manual-connection-info {
- font-weight: 700;
- line-height: 24px;
- margin: 10px;
-}
-
-#manual-connection-info-separator {
- height: 1px;
- background: #2E2E2E;
- margin-top: 3px;
- margin-bottom: 3px;
-}
-
-#qr-code {
- display: flex;
- margin: 20px auto;
- flex-direction: column;
- align-items: center;
- padding: 20px;
- background-color: white;
-}
-
-#scan-to-connect {
- margin-top: 20px;
- font-weight: bold;
-}
-
-#waiting-for-connection, #ips, #automatic-discovery {
- margin-top: 20px;
-}
-
#update-text {
margin-top: 20px;
width: 320px;
@@ -188,10 +49,6 @@ body, html {
display: none;
}
-#spinner {
- padding: 20px;
-}
-
#update-button-container {
display: flex;
flex-direction: row;
@@ -225,51 +82,3 @@ body, html {
background-position: 0 0;
}
}
-
-#window-can-be-closed {
- color: #666666;
- position: absolute;
- bottom: 0;
- margin-bottom: 20px;
-
- font-family: InterVariable;
- font-size: 18px;
- font-style: normal;
- font-weight: 400;
-}
-
-.lds-ring {
- display: inline-block;
- position: relative;
- width: 80px;
- height: 80px;
-}
-.lds-ring div {
- box-sizing: border-box;
- display: block;
- position: absolute;
- width: 64px;
- height: 64px;
- margin: 8px;
- border: 8px solid #fff;
- border-radius: 50%;
- animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
- border-color: #fff transparent transparent transparent;
-}
-.lds-ring div:nth-child(1) {
- animation-delay: -0.45s;
-}
-.lds-ring div:nth-child(2) {
- animation-delay: -0.3s;
-}
-.lds-ring div:nth-child(3) {
- animation-delay: -0.15s;
-}
-@keyframes lds-ring {
- 0% {
- transform: rotate(0deg);
- }
- 100% {
- transform: rotate(360deg);
- }
-}
diff --git a/receivers/electron/src/player/Preload.ts b/receivers/electron/src/player/Preload.ts
index e54c642..0f5b700 100644
--- a/receivers/electron/src/player/Preload.ts
+++ b/receivers/electron/src/player/Preload.ts
@@ -1,24 +1,8 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
import { contextBridge, ipcRenderer } from 'electron';
-import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from '../Packets';
-
-declare global {
- interface Window {
- electronAPI: any;
- }
-}
+import 'common/player/Preload';
contextBridge.exposeInMainWorld('electronAPI', {
isFullScreen: () => ipcRenderer.invoke('is-full-screen'),
toggleFullScreen: () => ipcRenderer.send('toggle-full-screen'),
exitFullScreen: () => ipcRenderer.send('exit-full-screen'),
- sendPlaybackError: (error: PlaybackErrorMessage) => ipcRenderer.send('send-playback-error', error),
- sendPlaybackUpdate: (update: PlaybackUpdateMessage) => ipcRenderer.send('send-playback-update', update),
- sendVolumeUpdate: (update: VolumeUpdateMessage) => ipcRenderer.send('send-volume-update', update),
- onPlay: (callback: any) => ipcRenderer.on("play", callback),
- onPause: (callback: any) => ipcRenderer.on("pause", callback),
- onResume: (callback: any) => ipcRenderer.on("resume", callback),
- onSeek: (callback: any) => ipcRenderer.on("seek", callback),
- onSetVolume: (callback: any) => ipcRenderer.on("setvolume", callback),
- onSetSpeed: (callback: any) => ipcRenderer.on("setspeed", callback)
});
diff --git a/receivers/electron/src/player/Renderer.ts b/receivers/electron/src/player/Renderer.ts
index 94d3dd5..761c052 100644
--- a/receivers/electron/src/player/Renderer.ts
+++ b/receivers/electron/src/player/Renderer.ts
@@ -1,483 +1,17 @@
-import dashjs from 'dashjs';
-import Hls, { LevelLoadedData } from 'hls.js';
-import { PlaybackUpdateMessage, PlayMessage, SeekMessage, SetSpeedMessage, SetVolumeMessage } from '../Packets';
-import { Player, PlayerType } from './Player';
+import { videoElement, PlayerControlEvent, playerCtrlStateUpdate } from 'common/player/Renderer';
-function formatDuration(duration: number) {
- 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 captionsBaseHeightCollapsed = 75;
+const captionsBaseHeightExpanded = 160;
+const captionsLineHeight = 34;
- 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: number) {
- const updateMessage = new PlaybackUpdateMessage(Date.now(), player.getCurrentTime(), player.getDuration(), updateState, player.getPlaybackRate());
-
- if (updateMessage.generationTime > lastPlayerUpdateGenerationTime) {
- lastPlayerUpdateGenerationTime = updateMessage.generationTime;
- window.electronAPI.sendPlaybackUpdate(updateMessage);
- }
-};
-
-function onPlayerLoad(value: PlayMessage, currentPlaybackRate?: number, currentVolume?: number) {
- playerCtrlStateUpdate(PlayerControlEvent.Load);
-
- // 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);
- } else if (currentPlaybackRate) {
- player.setPlaybackRate(currentPlaybackRate);
- } else {
- player.setPlaybackRate(1.0);
- }
- playerCtrlStateUpdate(PlayerControlEvent.SetPlaybackRate);
-
- if (currentVolume) {
- volumeChangeHandler(currentVolume);
- }
- else {
- // 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.electronAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: 1.0 });
- }
-
- player.play();
-}
-
-// HTML elements
-const videoElement = document.getElementById("videoPlayer") as HTMLVideoElement;
-const videoCaptions = document.getElementById("videoCaptions") as HTMLDivElement;
-
-const playerControls = document.getElementById("controls");
-
-const playerCtrlAction = document.getElementById("action");
-const playerCtrlVolume = document.getElementById("volume");
-
-const playerCtrlProgressBar = document.getElementById("progressBar");
-const playerCtrlProgressBarBuffer = document.getElementById("progressBarBuffer");
-const playerCtrlProgressBarProgress = document.getElementById("progressBarProgress");
-const playerCtrlProgressBarPosition = document.getElementById("progressBarPosition");
-const playerCtrlProgressBarHandle = document.getElementById("progressBarHandle");
-const PlayerCtrlProgressBarInteractiveArea = document.getElementById("progressBarInteractiveArea");
-
-const playerCtrlVolumeBar = document.getElementById("volumeBar");
-const playerCtrlVolumeBarProgress = document.getElementById("volumeBarProgress");
-const playerCtrlVolumeBarHandle = document.getElementById("volumeBarHandle");
-const playerCtrlVolumeBarInteractiveArea = document.getElementById("volumeBarInteractiveArea");
-
-const playerCtrlLiveBadge = document.getElementById("liveBadge");
-const playerCtrlPosition = document.getElementById("position");
-const playerCtrlDuration = document.getElementById("duration");
-
-const playerCtrlCaptions = document.getElementById("captions");
-const playerCtrlSpeed = document.getElementById("speed");
const playerCtrlFullscreen = document.getElementById("fullscreen");
+playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
+videoElement.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
-const playerCtrlSpeedMenu = document.getElementById("speedMenu");
-let playerCtrlSpeedMenuShown = false;
+export function targetPlayerCtrlStateUpdate(event: PlayerControlEvent): boolean {
+ let handledCase = false;
-
-const playbackRates = ["0.25", "0.50", "0.75", "1.00", "1.25", "1.50", "1.75", "2.00"];
-const playbackUpdateInterval = 1.0;
-const livePositionDelta = 5.0;
-const livePositionWindow = livePositionDelta * 4;
-let player: Player;
-let playerPrevTime: number = 0;
-let lastPlayerUpdateGenerationTime = 0;
-let isLive = false;
-let isLivePosition = false;
-
-
-window.electronAPI.onPlay((_event, value: PlayMessage) => {
- console.log("Handle play message renderer", JSON.stringify(value));
- const currentVolume = player ? player.getVolume() : null;
- const currentPlaybackRate = player ? player.getPlaybackRate() : null;
-
- playerPrevTime = 0;
- lastPlayerUpdateGenerationTime = 0;
- isLive = false;
- isLivePosition = false;
-
- if (player) {
- if (player.getSource() === value.url) {
- if (value.time) {
- if (Math.abs(value.time - player.getCurrentTime()) < 5000) {
- console.warn(`Skipped changing video URL because URL and time is (nearly) unchanged: ${value.url}, ${player.getSource()}, ${formatDuration(value.time)}, ${formatDuration(player.getCurrentTime())}`);
- } else {
- console.info(`Skipped changing video URL because URL is the same, but time was changed, seeking instead: ${value.url}, ${player.getSource()}, ${formatDuration(value.time)}, ${formatDuration(player.getCurrentTime())}`);
-
- player.setCurrentTime(value.time);
- }
- }
- return;
- }
-
- player.destroy();
- }
-
- if ((value.url || value.content) && value.container && videoElement) {
- if (value.container === 'application/dash+xml') {
- console.log("Loading dash player");
- const dashPlayer = dashjs.MediaPlayer().create();
- player = new Player(PlayerType.Dash, dashPlayer);
-
- dashPlayer.extend("RequestModifier", () => {
- return {
- modifyRequestHeader: function (xhr) {
- if (value.headers) {
- for (const [key, val] of Object.entries(value.headers)) {
- xhr.setRequestHeader(key, val);
- }
- }
-
- return xhr;
- }
- };
- }, true);
-
- // Player event handlers
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, () => { sendPlaybackUpdate(1); playerCtrlStateUpdate(PlayerControlEvent.Play); });
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PAUSED, () => { sendPlaybackUpdate(2); playerCtrlStateUpdate(PlayerControlEvent.Pause); });
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, () => { sendPlaybackUpdate(0) });
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_TIME_UPDATED, () => {
- playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate);
-
- if (Math.abs(dashPlayer.time() - playerPrevTime) >= playbackUpdateInterval) {
- sendPlaybackUpdate(dashPlayer.isPaused() ? 2 : 1);
- playerPrevTime = dashPlayer.time();
- }
- });
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_RATE_CHANGED, () => { sendPlaybackUpdate(dashPlayer.isPaused() ? 2 : 1) });
-
- // Buffering UI update when paused
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PROGRESS, () => { playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate); });
-
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_VOLUME_CHANGED, () => {
- const updateVolume = dashPlayer.isMuted() ? 0 : dashPlayer.getVolume();
- playerCtrlStateUpdate(PlayerControlEvent.VolumeChange);
- window.electronAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: updateVolume });
- });
-
- dashPlayer.on(dashjs.MediaPlayer.events.ERROR, (data) => { window.electronAPI.sendPlaybackError({
- message: `DashJS ERROR: ${JSON.stringify(data)}`
- })});
-
- dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ERROR, (data) => { window.electronAPI.sendPlaybackError({
- message: `DashJS PLAYBACK_ERROR: ${JSON.stringify(data)}`
- })});
-
- dashPlayer.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, () => { onPlayerLoad(value, currentPlaybackRate, currentVolume); });
-
- dashPlayer.on(dashjs.MediaPlayer.events.CUE_ENTER, (e: any) => {
- const subtitle = document.createElement("p")
- subtitle.setAttribute("id", "subtitle-" + e.cueID)
-
- subtitle.textContent = e.text;
- videoCaptions.appendChild(subtitle);
- });
-
- dashPlayer.on(dashjs.MediaPlayer.events.CUE_EXIT, (e: any) => {
- document.getElementById("subtitle-" + e.cueID)?.remove();
- });
-
- dashPlayer.updateSettings({
- // debug: {
- // logLevel: dashjs.LogLevel.LOG_LEVEL_INFO
- // },
- streaming: {
- text: {
- dispatchForManualRendering: true
- }
- }
- });
-
- if (value.content) {
- dashPlayer.initialize(videoElement, `data:${value.container};base64,` + window.btoa(value.content), true, value.time);
- // dashPlayer.initialize(videoElement, "https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/elephants_dream_480p_heaac5_1_https.mpd", true);
- } else {
- dashPlayer.initialize(videoElement, value.url, true, value.time);
- }
-
- } else if ((value.container === 'application/vnd.apple.mpegurl' || value.container === 'application/x-mpegURL') && !videoElement.canPlayType(value.container)) {
- console.log("Loading hls player");
-
- const config = {
- xhrSetup: function (xhr: XMLHttpRequest) {
- if (value.headers) {
- for (const [key, val] of Object.entries(value.headers)) {
- xhr.setRequestHeader(key, val);
- }
- }
- },
- };
-
- const hlsPlayer = new Hls(config);
-
- hlsPlayer.on(Hls.Events.ERROR, (eventName, data) => {
- window.electronAPI.sendPlaybackError({
- message: `HLS player error: ${JSON.stringify(data)}`
- });
- });
-
- hlsPlayer.on(Hls.Events.LEVEL_LOADED, (eventName, level: LevelLoadedData) => {
- isLive = level.details.live;
- isLivePosition = isLive ? true : false;
- });
-
- player = new Player(PlayerType.Hls, videoElement, hlsPlayer);
-
- // value.url = "https://devstreaming-cdn.apple.com/videos/streaming/examples/adv_dv_atmos/main.m3u8?ref=developerinsider.co";
- hlsPlayer.loadSource(value.url);
- hlsPlayer.attachMedia(videoElement);
- // hlsPlayer.subtitleDisplay = true;
-
- } else {
- console.log("Loading html player");
- player = new Player(PlayerType.Html, videoElement);
-
- videoElement.src = value.url;
- videoElement.load();
- }
-
- // Player event handlers
- if (player.playerType === PlayerType.Hls || player.playerType === PlayerType.Html) {
- videoElement.onplay = () => { sendPlaybackUpdate(1); playerCtrlStateUpdate(PlayerControlEvent.Play); };
- videoElement.onpause = () => { sendPlaybackUpdate(2); playerCtrlStateUpdate(PlayerControlEvent.Pause); };
- videoElement.onended = () => { sendPlaybackUpdate(0) };
- videoElement.ontimeupdate = () => {
- playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate);
-
- if (Math.abs(videoElement.currentTime - playerPrevTime) >= playbackUpdateInterval) {
- sendPlaybackUpdate(videoElement.paused ? 2 : 1);
- playerPrevTime = videoElement.currentTime;
- }
- };
- // Buffering UI update when paused
- videoElement.onprogress = () => { playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate); };
- videoElement.onratechange = () => { sendPlaybackUpdate(videoElement.paused ? 2 : 1) };
- videoElement.onvolumechange = () => {
- const updateVolume = videoElement.muted ? 0 : videoElement.volume;
- playerCtrlStateUpdate(PlayerControlEvent.VolumeChange);
- window.electronAPI.sendVolumeUpdate({ generationTime: Date.now(), volume: updateVolume });
- };
-
- videoElement.onerror = (event: Event | string, source?: string, lineno?: number, colno?: number, error?: Error) => {
- console.error("Player error", {source, lineno, colno, error});
- };
-
- videoElement.onloadedmetadata = () => { onPlayerLoad(value, currentPlaybackRate, currentVolume); };
- }
- }
-
- // Sender generated event handlers
- window.electronAPI.onPause(() => { player.pause(); });
- window.electronAPI.onResume(() => { player.play(); });
- window.electronAPI.onSeek((_event, value: SeekMessage) => { player.setCurrentTime(value.time); });
- window.electronAPI.onSetVolume((_event, value: SetVolumeMessage) => { volumeChangeHandler(value.volume); });
- window.electronAPI.onSetSpeed((_event, value: SetSpeedMessage) => { player.setPlaybackRate(value.speed); playerCtrlStateUpdate(PlayerControlEvent.SetPlaybackRate); });
-});
-
-let scrubbing = false;
-let volumeChanging = false;
-
-enum PlayerControlEvent {
- Load,
- Pause,
- Play,
- VolumeChange,
- TimeUpdate,
- UiFadeOut,
- UiFadeIn,
- SetCaptions,
- ToggleSpeedMenu,
- SetPlaybackRate,
- ToggleFullscreen,
- ExitFullscreen,
-}
-
-// UI update handlers
-function playerCtrlStateUpdate(event: PlayerControlEvent) {
switch (event) {
- case PlayerControlEvent.Load: {
- playerCtrlProgressBarBuffer.setAttribute("style", "width: 0px");
- playerCtrlProgressBarProgress.setAttribute("style", "width: 0px");
- playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetLeft}px`);
-
- const volume = Math.round(player.getVolume() * playerCtrlVolumeBar.offsetWidth);
- playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
- playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume + 8}px`);
-
- if (isLive) {
- playerCtrlLiveBadge.setAttribute("style", "display: block");
- playerCtrlPosition.setAttribute("style", "display: none");
- playerCtrlDuration.setAttribute("style", "display: none");
- }
- else {
- playerCtrlLiveBadge.setAttribute("style", "display: none");
- playerCtrlPosition.setAttribute("style", "display: block");
- playerCtrlDuration.setAttribute("style", "display: block");
- playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
- playerCtrlDuration.innerHTML = `/  ${formatDuration(player.getDuration())}`;
- }
-
- if (player.isCaptionsSupported()) {
- playerCtrlCaptions.setAttribute("style", "display: block");
- videoCaptions.setAttribute("style", "display: block");
- }
- else {
- playerCtrlCaptions.setAttribute("style", "display: none");
- videoCaptions.setAttribute("style", "display: none");
- player.enableCaptions(false);
- }
- playerCtrlStateUpdate(PlayerControlEvent.SetCaptions);
- break;
- }
-
- case PlayerControlEvent.Pause:
- playerCtrlAction.setAttribute("class", "play");
- stopUiHideTimer();
- break;
-
- case PlayerControlEvent.Play:
- playerCtrlAction.setAttribute("class", "pause");
- startUiHideTimer();
- break;
-
- case PlayerControlEvent.VolumeChange: {
- // console.log(`VolumeChange: isMute ${player.isMuted()}, volume: ${player.getVolume()}`);
- const volume = Math.round(player.getVolume() * playerCtrlVolumeBar.offsetWidth);
-
- if (player.isMuted()) {
- playerCtrlVolume.setAttribute("class", "mute");
- playerCtrlVolumeBarProgress.setAttribute("style", `width: 0px`);
- playerCtrlVolumeBarHandle.setAttribute("style", `left: 0px`);
- }
- else if (player.getVolume() >= 0.5) {
- playerCtrlVolume.setAttribute("class", "volume_high");
- playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
- playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume}px`);
- } else {
- playerCtrlVolume.setAttribute("class", "volume_low");
- playerCtrlVolumeBarProgress.setAttribute("style", `width: ${volume}px`);
- playerCtrlVolumeBarHandle.setAttribute("style", `left: ${volume}px`);
- }
- break;
- }
-
- case PlayerControlEvent.TimeUpdate: {
- // console.log(`TimeUpdate: Position: ${player.getCurrentTime()}, Duration: ${player.getDuration()}`);
-
- if (isLive) {
- if (isLivePosition && player.getDuration() - player.getCurrentTime() > livePositionWindow) {
- isLivePosition = false;
- playerCtrlLiveBadge.setAttribute("style", `background-color: #595959`);
- }
- else if (!isLivePosition && player.getDuration() - player.getCurrentTime() <= livePositionWindow) {
- isLivePosition = true;
- playerCtrlLiveBadge.setAttribute("style", `background-color: red`);
- }
- }
-
- if (isLivePosition) {
- playerCtrlProgressBarProgress.setAttribute("style", `width: ${playerCtrlProgressBar.offsetWidth}px`);
- playerCtrlProgressBarHandle.setAttribute("style", `left: ${playerCtrlProgressBar.offsetWidth + playerCtrlProgressBar.offsetLeft}px`);
- }
- else {
- const buffer = Math.round((player.getBufferLength() / player.getDuration()) * playerCtrlProgressBar.offsetWidth);
- const progress = Math.round((player.getCurrentTime() / player.getDuration()) * playerCtrlProgressBar.offsetWidth);
- const handle = progress + playerCtrlProgressBar.offsetLeft;
-
- playerCtrlProgressBarBuffer.setAttribute("style", `width: ${buffer}px`);
- playerCtrlProgressBarProgress.setAttribute("style", `width: ${progress}px`);
- playerCtrlProgressBarHandle.setAttribute("style", `left: ${handle}px`);
-
- playerCtrlPosition.textContent = formatDuration(player.getCurrentTime());
- }
-
- break;
- }
-
- case PlayerControlEvent.UiFadeOut:
- document.body.style.cursor = "none";
- playerControls.setAttribute("style", "opacity: 0");
-
- if (player.isCaptionsEnabled()) {
- videoCaptions.setAttribute("style", "display: block; bottom: 75px;");
- } else {
- videoCaptions.setAttribute("style", "display: none; bottom: 75px;");
- }
-
-
- break;
-
- case PlayerControlEvent.UiFadeIn:
- document.body.style.cursor = "default";
- playerControls.setAttribute("style", "opacity: 1");
-
- if (player.isCaptionsEnabled()) {
- videoCaptions.setAttribute("style", "display: block; bottom: 160px;");
- } else {
- videoCaptions.setAttribute("style", "display: none; bottom: 160px;");
- }
-
- break;
-
- case PlayerControlEvent.SetCaptions:
- if (player.isCaptionsEnabled()) {
- playerCtrlCaptions.setAttribute("class", "captions_on");
- videoCaptions.setAttribute("style", "display: block");
- } else {
- playerCtrlCaptions.setAttribute("class", "captions_off");
- videoCaptions.setAttribute("style", "display: none");
- }
-
- break;
-
- case PlayerControlEvent.ToggleSpeedMenu: {
- if (playerCtrlSpeedMenuShown) {
- playerCtrlSpeedMenu.setAttribute("style", "display: none");
- } else {
- playerCtrlSpeedMenu.setAttribute("style", "display: block");
- }
-
- playerCtrlSpeedMenuShown = !playerCtrlSpeedMenuShown;
- break;
- }
-
- case PlayerControlEvent.SetPlaybackRate: {
- const rate = player.getPlaybackRate().toFixed(2);
- const entryElement = document.getElementById(`speedMenuEntry_${rate}_enabled`);
-
- playbackRates.forEach(r => {
- const entry = document.getElementById(`speedMenuEntry_${r}_enabled`);
- entry.setAttribute("style", "opacity: 0");
- });
-
- // Ignore updating GUI for custom rates
- if (entryElement !== null) {
- entryElement.setAttribute("style", "opacity: 1");
- }
-
- break;
- }
-
case PlayerControlEvent.ToggleFullscreen: {
window.electronAPI.toggleFullScreen();
@@ -489,269 +23,49 @@ function playerCtrlStateUpdate(event: PlayerControlEvent) {
}
});
+ handledCase = true;
break;
}
case PlayerControlEvent.ExitFullscreen:
window.electronAPI.exitFullScreen();
playerCtrlFullscreen.setAttribute("class", "fullscreen_off");
+
+ handledCase = true;
break;
default:
break;
}
+
+ return handledCase;
}
-function scrubbingMouseUIHandler(e: MouseEvent) {
- const progressBarOffset = e.offsetX - 8;
- const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
- let time = isLive ? Math.round((1 - (progressBarOffset / progressBarWidth)) * player.getDuration()) : Math.round((progressBarOffset / progressBarWidth) * player.getDuration());
- time = Math.min(player.getDuration(), Math.max(0.0, time));
-
- if (scrubbing && isLive && e.buttons === 1) {
- isLivePosition = false;
- playerCtrlLiveBadge.setAttribute("style", `background-color: #595959`);
- }
-
- const livePrefix = isLive && Math.floor(time) !== 0 ? "-" : "";
- playerCtrlProgressBarPosition.textContent = isLive ? `${livePrefix}${formatDuration(time)}` : formatDuration(time);
-
- let offset = e.offsetX - (playerCtrlProgressBarPosition.offsetWidth / 2);
- offset = Math.min(PlayerCtrlProgressBarInteractiveArea.offsetWidth - (playerCtrlProgressBarPosition.offsetWidth / 1), Math.max(8, offset));
- playerCtrlProgressBarPosition.setAttribute("style", `display: block; left: ${offset}px`);
-}
-
-// Receiver generated event handlers
-playerCtrlAction.onclick = () => {
- if (player.isPaused()) {
- player.play();
- } else {
- player.pause();
- }
-};
-
-playerCtrlVolume.onclick = () => { player.setMute(!player.isMuted()); };
-
-PlayerCtrlProgressBarInteractiveArea.onmousedown = (e: MouseEvent) => { scrubbing = true; scrubbingMouseHandler(e) };
-PlayerCtrlProgressBarInteractiveArea.onmouseup = () => { scrubbing = false; };
-PlayerCtrlProgressBarInteractiveArea.onmouseenter = (e: MouseEvent) => {
- if (e.buttons === 0) {
- volumeChanging = false;
- }
-
- scrubbingMouseUIHandler(e);
-};
-PlayerCtrlProgressBarInteractiveArea.onmouseleave = () => { playerCtrlProgressBarPosition.setAttribute("style", "display: none"); };
-PlayerCtrlProgressBarInteractiveArea.onmousemove = (e: MouseEvent) => { scrubbingMouseHandler(e) };
-
-function scrubbingMouseHandler(e: MouseEvent) {
- const progressBarOffset = e.offsetX - 8;
- const progressBarWidth = PlayerCtrlProgressBarInteractiveArea.offsetWidth - 16;
- let time = Math.round((progressBarOffset / progressBarWidth) * player.getDuration());
- time = Math.min(player.getDuration(), Math.max(0.0, time));
-
- if (scrubbing && e.buttons === 1) {
- player.setCurrentTime(time);
- }
-
- scrubbingMouseUIHandler(e);
-}
-
-playerCtrlVolumeBarInteractiveArea.onmousedown = (e: MouseEvent) => { volumeChanging = true; volumeChangeMouseHandler(e) };
-playerCtrlVolumeBarInteractiveArea.onmouseup = () => { volumeChanging = false; };
-playerCtrlVolumeBarInteractiveArea.onmouseenter = (e: MouseEvent) => {
- if (e.buttons === 0) {
- scrubbing = false;
- }
-};
-playerCtrlVolumeBarInteractiveArea.onmousemove = (e: MouseEvent) => { volumeChangeMouseHandler(e) };
-playerCtrlVolumeBarInteractiveArea.onwheel = (e: WheelEvent) => {
- const delta = -e.deltaY;
-
- if (delta > 0 ) {
- volumeChangeHandler(Math.min(player.getVolume() + volumeIncrement, 1));
- } else if (delta < 0) {
- volumeChangeHandler(Math.max(player.getVolume() - volumeIncrement, 0));
- }
-};
-
-function volumeChangeMouseHandler(e: MouseEvent) {
- if (volumeChanging && e.buttons === 1) {
- const volumeBarOffsetX = e.offsetX - 8;
- const volumeBarWidth = playerCtrlVolumeBarInteractiveArea.offsetWidth - 16;
- const volume = volumeBarOffsetX / volumeBarWidth;
- volumeChangeHandler(volume);
- }
-}
-
-function volumeChangeHandler(volume: number) {
- if (!player.isMuted() && volume <= 0) {
- player.setMute(true);
- }
- else if (player.isMuted() && volume > 0) {
- player.setMute(false);
- }
-
- player.setVolume(volume);
-}
-
-playerCtrlLiveBadge.onclick = () => { setLivePosition(); };
-
-function setLivePosition() {
- if (!isLivePosition) {
- isLivePosition = true;
-
- player.setCurrentTime(player.getDuration() - livePositionDelta);
- playerCtrlLiveBadge.setAttribute("style", `background-color: red`);
-
- if (player.isPaused()) {
- player.play();
- }
- }
-}
-
-playerCtrlCaptions.onclick = () => { player.enableCaptions(!player.isCaptionsEnabled()); playerCtrlStateUpdate(PlayerControlEvent.SetCaptions); };
-playerCtrlSpeed.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleSpeedMenu); };
-playerCtrlFullscreen.onclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
-
-playbackRates.forEach(r => {
- const entry = document.getElementById(`speedMenuEntry_${r}`);
- entry.onclick = () => {
- player.setPlaybackRate(parseFloat(r));
- playerCtrlStateUpdate(PlayerControlEvent.SetPlaybackRate);
- playerCtrlStateUpdate(PlayerControlEvent.ToggleSpeedMenu);
- };
-});
-
-videoElement.onclick = () => {
- if (!playerCtrlSpeedMenuShown) {
- if (player.isPaused()) {
- player.play();
- } else {
- player.pause();
- }
- }
-};
-videoElement.ondblclick = () => { playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen); };
-
-// Component hiding
-let uiHideTimer = null;
-let uiVisible = true;
-
-function startUiHideTimer() {
- if (uiHideTimer === null) {
- uiHideTimer = window.setTimeout(() => {
- uiHideTimer = null;
- uiVisible = false;
- playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
- }, 3000);
- }
-}
-
-function stopUiHideTimer() {
- if (uiHideTimer) {
- window.clearTimeout(uiHideTimer);
- uiHideTimer = null;
- }
-
- if (!uiVisible) {
- uiVisible = true;
- playerCtrlStateUpdate(PlayerControlEvent.UiFadeIn);
- }
-}
-
-document.onmouseout = () => {
- if (uiHideTimer) {
- window.clearTimeout(uiHideTimer);
- uiHideTimer = null;
- }
-
- uiVisible = false;
- playerCtrlStateUpdate(PlayerControlEvent.UiFadeOut);
-}
-
-document.onmousemove = () => {
- stopUiHideTimer();
-
- if (player && !player.isPaused()) {
- startUiHideTimer();
- }
-};
-
-window.onresize = () => { playerCtrlStateUpdate(PlayerControlEvent.TimeUpdate); };
-
-// Listener for hiding speed menu when clicking outside element
-document.addEventListener('click', (event: MouseEvent) => {
- const node = event.target as Node;
- if (playerCtrlSpeedMenuShown && !playerCtrlSpeed.contains(node) && !playerCtrlSpeedMenu.contains(node)){
- playerCtrlStateUpdate(PlayerControlEvent.ToggleSpeedMenu);
- }
-});
-
-// Add the keydown event listener to the document
-const skipInterval = 10;
-const volumeIncrement = 0.1;
-
-document.addEventListener('keydown', (event) => {
-// console.log("KeyDown", event);
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function targetKeyDownEventListener(event: any): boolean {
+ let handledCase = false;
switch (event.code) {
case 'KeyF':
case 'F11':
playerCtrlStateUpdate(PlayerControlEvent.ToggleFullscreen);
event.preventDefault();
+ handledCase = true;
break;
case 'Escape':
playerCtrlStateUpdate(PlayerControlEvent.ExitFullscreen);
event.preventDefault();
+ handledCase = true;
break;
- case 'ArrowLeft':
- // Skip back
- player.setCurrentTime(Math.max(player.getCurrentTime() - skipInterval, 0));
- event.preventDefault();
- break;
- case 'ArrowRight':
- // Skip forward
- if (!isLivePosition) {
- player.setCurrentTime(Math.min(player.getCurrentTime() + skipInterval, player.getDuration()));
- }
- event.preventDefault();
- break;
- case "Home":
- player.setCurrentTime(0);
- event.preventDefault();
- break;
- case "End":
- if (isLive) {
- setLivePosition();
- }
- else {
- player.setCurrentTime(player.getDuration());
- }
- event.preventDefault();
- break;
- case 'KeyK':
- case 'Space':
- case 'Enter':
- // Pause/Continue
- if (player.isPaused()) {
- player.play();
- } else {
- player.pause();
- }
- event.preventDefault();
- break;
- case 'KeyM':
- // Mute toggle
- player.setMute(!player.isMuted());
- break;
- case 'ArrowUp':
- // Volume up
- volumeChangeHandler(Math.min(player.getVolume() + volumeIncrement, 1));
- break;
- case 'ArrowDown':
- // Volume down
- volumeChangeHandler(Math.max(player.getVolume() - volumeIncrement, 0));
+ default:
break;
}
-});
+
+ return handledCase
+};
+
+export {
+ captionsBaseHeightCollapsed,
+ captionsBaseHeightExpanded,
+ captionsLineHeight,
+}
diff --git a/receivers/electron/src/player/index.html b/receivers/electron/src/player/index.html
index 3ddd6b1..05b60f4 100644
--- a/receivers/electron/src/player/index.html
+++ b/receivers/electron/src/player/index.html
@@ -1,10 +1,11 @@
-