diff --git a/chrome/_locales/en/messages.json b/chrome/_locales/en/messages.json
index 87eb03e3..250a5491 100644
--- a/chrome/_locales/en/messages.json
+++ b/chrome/_locales/en/messages.json
@@ -711,15 +711,36 @@
"options_general_playbackrate": {
"message": "Default playback rate"
},
- "options_general_clickpause": {
- "message": "Single click toggles play/pause"
- },
"options_general_autoplayyt": {
"message": "Autoplay YouTube videos"
},
"options_general_quality": {
"message": "Quality multiplier factor for quality selection algorithm (higher will choose higher quality)"
},
+ "options_general_clickaction": {
+ "message": "Single click action"
+ },
+ "options_general_dblclickaction": {
+ "message": "Double click action"
+ },
+ "options_general_tplclickaction": {
+ "message": "Triple click action"
+ },
+ "options_general_clickaction_playpause": {
+ "message": "Toggle play/pause"
+ },
+ "options_general_clickaction_fullscreen": {
+ "message": "Toggle fullscreen"
+ },
+ "options_general_clickaction_pip": {
+ "message": "Toggle picture-in-picture"
+ },
+ "options_general_clickaction_hidecontrols": {
+ "message": "Hide controls"
+ },
+ "options_general_clickaction_hideplayer": {
+ "message": "Hide player"
+ },
"options_export_header": {
"message": "Import/Export Settings"
},
diff --git a/chrome/_locales/es/messages.json b/chrome/_locales/es/messages.json
index 9ba9df91..938acd4c 100644
--- a/chrome/_locales/es/messages.json
+++ b/chrome/_locales/es/messages.json
@@ -710,15 +710,36 @@
"options_general_playbackrate": {
"message": "Velocidad de reproducción predeterminada"
},
- "options_general_clickpause": {
- "message": "Un solo clic alterna reproducir/pausar"
- },
"options_general_autoplayyt": {
"message": "Reproducir automáticamente videos de YouTube incrustados"
},
"options_general_quality": {
"message": "Factor multiplicador de calidad para el algoritmo de selección de calidad (mayor elegirá mayor calidad)"
},
+ "options_general_clickaction": {
+ "message": "Acción de clic"
+ },
+ "options_general_dblclickaction": {
+ "message": "Acción de doble clic"
+ },
+ "options_general_tplclickaction": {
+ "message": "Acción de triple clic"
+ },
+ "options_general_clickaction_playpause": {
+ "message": "Alternar reproducir/pausar"
+ },
+ "options_general_clickaction_fullscreen": {
+ "message": "Alternar pantalla completa"
+ },
+ "options_general_clickaction_pip": {
+ "message": "Alternar imagen en imagen"
+ },
+ "options_general_clickaction_hidecontrols": {
+ "message": "Ocultar controles"
+ },
+ "options_general_clickaction_hideplayer": {
+ "message": "Ocultar reproductor"
+ },
"options_export_header": {
"message": "Importar/Exportar Configuraciones"
},
diff --git a/chrome/_locales/ja/messages.json b/chrome/_locales/ja/messages.json
index 2ec82aba..e292e86c 100644
--- a/chrome/_locales/ja/messages.json
+++ b/chrome/_locales/ja/messages.json
@@ -711,15 +711,36 @@
"options_general_playbackrate": {
"message": "デフォルトの再生速度"
},
- "options_general_clickpause": {
- "message": "シングルクリックで再生/一時停止を切り替えます"
- },
"options_general_autoplayyt": {
"message": "YouTube 動画の自動再生"
},
"options_general_quality": {
"message": "品質選択アルゴリズムの品質倍率係数 (高いほど高品質を選択します)"
},
+ "options_general_clickaction": {
+ "message": "シングルクリックアクション"
+ },
+ "options_general_dblclickaction": {
+ "message": "ダブルクリックアクション"
+ },
+ "options_general_tplclickaction": {
+ "message": "トリプルクリックアクション"
+ },
+ "options_general_clickaction_playpause": {
+ "message": "再生/一時停止を切り替えます"
+ },
+ "options_general_clickaction_fullscreen": {
+ "message": "フルスクリーンを切り替えます"
+ },
+ "options_general_clickaction_pip": {
+ "message": "ピクチャー イン ピクチャー (PiP) を切り替えます"
+ },
+ "options_general_clickaction_hidecontrols": {
+ "message": "コントロールを隠します"
+ },
+ "options_general_clickaction_hideplayer": {
+ "message": "プレーヤーを隠します"
+ },
"options_export_header": {
"message": "設定のインポート/エクスポート"
},
diff --git a/chrome/player/FastStreamClient.mjs b/chrome/player/FastStreamClient.mjs
index 3e44fb6a..a8a52654 100644
--- a/chrome/player/FastStreamClient.mjs
+++ b/chrome/player/FastStreamClient.mjs
@@ -14,6 +14,7 @@ import {DOMElements} from './ui/DOMElements.mjs';
import {AudioConfigManager} from './ui/audio/AudioConfigManager.mjs';
import {EnvUtils} from './utils/EnvUtils.mjs';
import {Localize} from './modules/Localize.mjs';
+import {ClickActions} from './options/defaults/ClickActions.mjs';
export class FastStreamClient extends EventEmitter {
@@ -31,7 +32,9 @@ export class FastStreamClient extends EventEmitter {
freeFragments: true,
downloadAll: false,
freeUnusedChannels: true,
- clickToPause: false,
+ singleClickAction: ClickActions.HIDE_CONTROLS,
+ doubleClickAction: ClickActions.PLAY_PAUSE,
+ tripleClickAction: ClickActions.FULLSCREEN,
videoBrightness: 1,
videoContrast: 1,
videoSaturation: 1,
@@ -116,9 +119,11 @@ export class FastStreamClient extends EventEmitter {
this.options.downloadAll = options.downloadAll;
this.options.freeUnusedChannels = options.freeUnusedChannels;
this.options.autoEnableBestSubtitles = options.autoEnableBestSubtitles;
- this.options.clickToPause = options.clickToPause;
this.options.maxSpeed = options.maxSpeed;
this.options.seekStepSize = options.seekStepSize;
+ this.options.singleClickAction = options.singleClickAction;
+ this.options.doubleClickAction = options.doubleClickAction;
+ this.options.tripleClickAction = options.tripleClickAction;
this.options.videoBrightness = options.videoBrightness;
this.options.videoContrast = options.videoContrast;
diff --git a/chrome/player/options/defaults/ClickActions.mjs b/chrome/player/options/defaults/ClickActions.mjs
new file mode 100644
index 00000000..cffc0a1d
--- /dev/null
+++ b/chrome/player/options/defaults/ClickActions.mjs
@@ -0,0 +1,7 @@
+export const ClickActions = {
+ PLAY_PAUSE: 'playpause',
+ FULLSCREEN: 'fullscreen',
+ PIP: 'pip',
+ HIDE_CONTROLS: 'hidecontrols',
+ HIDE_PLAYER: 'hideplayer',
+};
diff --git a/chrome/player/options/defaults/DefaultOptions.mjs b/chrome/player/options/defaults/DefaultOptions.mjs
index 1090c652..a5544a2b 100644
--- a/chrome/player/options/defaults/DefaultOptions.mjs
+++ b/chrome/player/options/defaults/DefaultOptions.mjs
@@ -1,4 +1,5 @@
import {EnvUtils} from '../../utils/EnvUtils.mjs';
+import {ClickActions} from './ClickActions.mjs';
import {DefaultKeybinds} from './DefaultKeybinds.mjs';
export const DefaultOptions = {
@@ -8,7 +9,6 @@ export const DefaultOptions = {
downloadAll: true,
freeUnusedChannels: true,
autoEnableBestSubtitles: false,
- clickToPause: false,
autoplayYoutube: true,
autoEnableURLs: [],
keybinds: DefaultKeybinds,
@@ -23,4 +23,7 @@ export const DefaultOptions = {
seekStepSize: 2,
playbackRate: 1,
qualityMultiplier: 1.1,
+ singleClickAction: ClickActions.HIDE_CONTROLS,
+ doubleClickAction: ClickActions.PLAY_PAUSE,
+ tripleClickAction: ClickActions.FULLSCREEN,
};
diff --git a/chrome/player/options/index.html b/chrome/player/options/index.html
index ecfc2fc1..8c26e60d 100644
--- a/chrome/player/options/index.html
+++ b/chrome/player/options/index.html
@@ -127,13 +127,6 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/chrome/player/options/options.css b/chrome/player/options/options.css
index 71ff992d..8e55ddcb 100644
--- a/chrome/player/options/options.css
+++ b/chrome/player/options/options.css
@@ -130,7 +130,7 @@ p.hint {
button {
background-color: rgba(255, 255, 255, 0.4);
- border: 1px solid rgba(0, 0, 0, 0.4);
+ border: 1px solid rgba(0, 0, 0, 0.7);
border-radius: 3px;
cursor: pointer;
padding: 2px 5px;
@@ -219,7 +219,7 @@ button:hover {
.option.grid2 {
display: grid;
gap: 5px;
- grid-template-columns: 1fr 150px;
+ grid-template-columns: 1fr 160px;
align-items: center;
}
@@ -232,6 +232,34 @@ button:hover {
text-align: center;
}
+input {
+ border: 1px solid rgba(0, 0, 0, 0.7);
+ border-radius: 3px;
+ padding: 3px 5px;
+ outline: none;
+ background-color: white;
+ font-size: 12px;
+}
+
+input:focus {
+ border: 1px solid blue;
+}
+
+.option.grid2 .select {
+ grid-column: 2;
+}
+
+.select > select {
+ width: 100%;
+ height: 100%;
+ border: 1px solid rgba(0, 0, 0, 0.7);
+ border-radius: 3px;
+ padding: 2px 5px;
+ outline: none;
+ background-color: white;
+ font-size: 12px;
+}
+
input[type="checkbox"] {
width: 16px;
height: 16px;
diff --git a/chrome/player/options/options.mjs b/chrome/player/options/options.mjs
index b8b601de..8d68e54c 100644
--- a/chrome/player/options/options.mjs
+++ b/chrome/player/options/options.mjs
@@ -7,6 +7,7 @@ import {DefaultOptions} from './defaults/DefaultOptions.mjs';
import {Localize} from '../modules/Localize.mjs';
import {UpdateChecker} from '../utils/UpdateChecker.mjs'; // SPLICER:NO_UPDATE_CHECKER:REMOVE_LINE
+import {ClickActions} from './defaults/ClickActions.mjs';
let Options = {};
const analyzeVideos = document.getElementById('analyzevideos');
@@ -20,11 +21,13 @@ const autoSub = document.getElementById('autosub');
const maxSpeed = document.getElementById('maxspeed');
const seekStepSize = document.getElementById('seekstepsize');
const playbackRate = document.getElementById('playbackrate');
-const clickToPause = document.getElementById('clicktopause');
const autoplayYoutube = document.getElementById('autoplayyt');
const qualityMultiplier = document.getElementById('qualitymultiplier');
const importButton = document.getElementById('import');
const exportButton = document.getElementById('export');
+const clickAction = document.getElementById('clickaction');
+const dblclickAction = document.getElementById('dblclickaction');
+const tplclickAction = document.getElementById('tplclickaction');
autoEnableURLSInput.setAttribute('autocapitalize', 'off');
autoEnableURLSInput.setAttribute('autocomplete', 'off');
autoEnableURLSInput.setAttribute('autocorrect', 'off');
@@ -52,13 +55,16 @@ async function loadOptions(newOptions) {
playStreamURLs.checked = !!Options.playStreamURLs;
playMP4URLs.checked = !!Options.playMP4URLs;
autoSub.checked = !!Options.autoEnableBestSubtitles;
- clickToPause.checked = !!Options.clickToPause;
autoplayYoutube.checked = !!Options.autoplayYoutube;
maxSpeed.value = StringUtils.getSpeedString(Options.maxSpeed);
seekStepSize.value = Math.round(Options.seekStepSize * 100) / 100;
playbackRate.value = Options.playbackRate;
qualityMultiplier.value = Options.qualityMultiplier;
+ setSelectMenuValue(clickAction, Options.singleClickAction);
+ setSelectMenuValue(dblclickAction, Options.doubleClickAction);
+ setSelectMenuValue(tplclickAction, Options.tripleClickAction);
+
if (Options.keybinds) {
keybindsList.replaceChildren();
for (const keybind in Options.keybinds) {
@@ -82,15 +88,59 @@ async function loadOptions(newOptions) {
autoEnableURLSInput.value = Options.autoEnableURLs.join('\n');
}
+function createSelectMenu(container, options, selected, localPrefix, callback) {
+ container.replaceChildren();
+ const select = document.createElement('select');
+ for (const option of options) {
+ const optionElement = document.createElement('option');
+ optionElement.value = option;
+ optionElement.textContent = Localize.getMessage(localPrefix + '_' + option);
+ if (option === selected) {
+ optionElement.selected = true;
+ }
+ select.appendChild(optionElement);
+ }
+ select.addEventListener('change', callback);
+ container.appendChild(select);
+}
+
+function setSelectMenuValue(container, value) {
+ const select = container.querySelector('select');
+ if (!select) {
+ return;
+ }
+ select.value = value;
+}
+
+createSelectMenu(clickAction, Object.values(ClickActions), Options.singleClickAction, 'options_general_clickaction', (e) => {
+ Options.singleClickAction = e.target.value;
+ optionChanged();
+});
+
+createSelectMenu(dblclickAction, Object.values(ClickActions), Options.doubleClickAction, 'options_general_clickaction', (e) => {
+ Options.doubleClickAction = e.target.value;
+ optionChanged();
+});
+
+createSelectMenu(tplclickAction, Object.values(ClickActions), Options.tripleClickAction, 'options_general_clickaction', (e) => {
+ Options.tripleClickAction = e.target.value;
+ optionChanged();
+});
+
document.querySelectorAll('.option').forEach((option) => {
option.addEventListener('click', (e) => {
if (e.target.tagName !== 'INPUT') {
const input = option.querySelector('input');
- input.click();
+ if (input) {
+ input.click();
+ }
}
});
- WebUtils.setupTabIndex(option.querySelector('input'));
+ const input = option.querySelector('input');
+ if (input) {
+ WebUtils.setupTabIndex(input);
+ }
});
document.querySelectorAll('.video-option').forEach((option) => {
@@ -206,11 +256,6 @@ autoplayYoutube.addEventListener('change', () => {
optionChanged();
});
-clickToPause.addEventListener('change', () => {
- Options.clickToPause = clickToPause.checked;
- optionChanged();
-});
-
maxSpeed.addEventListener('change', () => {
// parse value, number unit/s
Options.maxSpeed = StringUtils.getSpeedValue(maxSpeed.value);
diff --git a/chrome/player/ui/InterfaceController.mjs b/chrome/player/ui/InterfaceController.mjs
index fcfeb4be..a6b57c75 100644
--- a/chrome/player/ui/InterfaceController.mjs
+++ b/chrome/player/ui/InterfaceController.mjs
@@ -3,6 +3,7 @@ import {PlayerModes} from '../enums/PlayerModes.mjs';
import {Coloris} from '../modules/coloris.mjs';
import {Localize} from '../modules/Localize.mjs';
import {streamSaver} from '../modules/StreamSaver.mjs';
+import {ClickActions} from '../options/defaults/ClickActions.mjs';
import {SubtitleTrack} from '../SubtitleTrack.mjs';
import {EnvUtils} from '../utils/EnvUtils.mjs';
import {FastStreamArchiveUtils} from '../utils/FastStreamArchiveUtils.mjs';
@@ -20,6 +21,8 @@ export class InterfaceController {
this.client = client;
this.persistent = client.persistent;
this.isSeeking = false;
+ this.hidden = false;
+ this.shouldPlay = false;
this.isMouseOverProgressbar = false;
this.lastSpeed = 0;
@@ -421,14 +424,6 @@ export class InterfaceController {
this.playPauseToggle();
e.stopPropagation();
});
- DOMElements.videoContainer.addEventListener('dblclick', (e) => {
- if (!this.client.options.clickToPause) {
- this.playPauseToggle();
- } else {
- this.hideControlBarOnAction();
- }
- e.stopPropagation();
- });
DOMElements.progressContainer.addEventListener('mousedown', this.onProgressbarMouseDown.bind(this));
DOMElements.progressContainer.addEventListener('mouseenter', this.onProgressbarMouseEnter.bind(this));
DOMElements.progressContainer.addEventListener('mouseleave', this.onProgressbarMouseLeave.bind(this));
@@ -590,17 +585,54 @@ export class InterfaceController {
this.focusingControls = false;
this.queueControlsHide();
});
- DOMElements.videoContainer.addEventListener('click', () => {
+ let clickCount = 0;
+ let clickTimeout = null;
+ DOMElements.videoContainer.addEventListener('click', (e) => {
if (this.isBigPlayButtonVisible()) {
this.playPauseToggle();
- } else if (this.client.options.clickToPause) {
- this.playPauseToggle();
return;
}
- this.focusingControls = false;
- this.mouseOverControls = false;
- this.hideControlBarOnAction();
+ if (clickTimeout !== null) {
+ clickCount++;
+ } else {
+ clickCount = 1;
+ }
+ clearTimeout(clickTimeout);
+ clickTimeout = setTimeout(() => {
+ clickTimeout = null;
+
+ let clickAction;
+ if (clickCount === 1) {
+ clickAction = this.client.options.singleClickAction;
+ } else if (clickCount === 2) {
+ clickAction = this.client.options.doubleClickAction;
+ } else if (clickCount === 3) {
+ clickAction = this.client.options.tripleClickAction;
+ } else {
+ return;
+ }
+
+ switch (clickAction) {
+ case ClickActions.FULLSCREEN:
+ this.fullscreenToggle();
+ break;
+ case ClickActions.PIP:
+ this.pipToggle();
+ break;
+ case ClickActions.PLAY_PAUSE:
+ this.playPauseToggle();
+ break;
+ case ClickActions.HIDE_CONTROLS:
+ this.focusingControls = false;
+ this.mouseOverControls = false;
+ this.hideControlBar();
+ break;
+ case ClickActions.HIDE_PLAYER:
+ this.toggleHide();
+ break;
+ }
+ }, clickCount < 3 ? 300 : 0);
});
DOMElements.hideButton.addEventListener('click', () => {
DOMElements.hideButton.blur();
@@ -694,6 +726,22 @@ export class InterfaceController {
DOMElements.playinfo.style.display = this.client.player ? 'none' : '';
}
+ toggleHide() {
+ if (this.hidden) {
+ DOMElements.playerContainer.classList.remove('player-hidden');
+ this.hidden = false;
+ if (this.shouldPlay) {
+ this.client.player?.play();
+ }
+ } else {
+ DOMElements.playerContainer.classList.add('player-hidden');
+
+ this.hidden = true;
+ this.shouldPlay = this.client.persistent.playing;
+ this.client.player?.pause();
+ }
+ }
+
pipToggle() {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
diff --git a/chrome/player/ui/KeybindManager.mjs b/chrome/player/ui/KeybindManager.mjs
index 74080cfc..f52ab20d 100644
--- a/chrome/player/ui/KeybindManager.mjs
+++ b/chrome/player/ui/KeybindManager.mjs
@@ -1,13 +1,11 @@
import {DefaultKeybinds} from '../options/defaults/DefaultKeybinds.mjs';
import {EventEmitter} from '../modules/eventemitter.mjs';
import {WebUtils} from '../utils/WebUtils.mjs';
-import {DOMElements} from './DOMElements.mjs';
export class KeybindManager extends EventEmitter {
constructor(client) {
super();
this.client = client;
- this.hidden = false;
this.keybindMap = new Map();
this.setup();
}
@@ -22,21 +20,8 @@ export class KeybindManager extends EventEmitter {
this.onKeyDown(e);
});
- let shouldPlay = false;
this.on('HidePlayer', (e) => {
- if (this.hidden) {
- DOMElements.playerContainer.classList.remove('player-hidden');
- this.hidden = false;
- if (shouldPlay) {
- this.client.player?.play();
- }
- } else {
- DOMElements.playerContainer.classList.add('player-hidden');
-
- this.hidden = true;
- shouldPlay = this.client.persistent.playing;
- this.client.player?.pause();
- }
+ this.client.interfaceController.toggleHide();
});
this.on('GoToStart', (e) => {