diff --git a/chrome/_locales/de/messages.json b/chrome/_locales/de/messages.json index 2de6b9ae..2c7da548 100644 --- a/chrome/_locales/de/messages.json +++ b/chrome/_locales/de/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/en/messages.json b/chrome/_locales/en/messages.json index 78b12834..f187278b 100644 --- a/chrome/_locales/en/messages.json +++ b/chrome/_locales/en/messages.json @@ -899,6 +899,9 @@ "options_video_hue": { "message": "Hue Rotate" }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" + }, "options_video_daltonizer_type": { "message": "Color blindness correction" }, diff --git a/chrome/_locales/es/messages.json b/chrome/_locales/es/messages.json index dfa309db..49ce6c40 100644 --- a/chrome/_locales/es/messages.json +++ b/chrome/_locales/es/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "Aceptar" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/fr/messages.json b/chrome/_locales/fr/messages.json index 4b57198b..e2852e24 100644 --- a/chrome/_locales/fr/messages.json +++ b/chrome/_locales/fr/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/id/messages.json b/chrome/_locales/id/messages.json index 5182137e..b8a33733 100644 --- a/chrome/_locales/id/messages.json +++ b/chrome/_locales/id/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/it/messages.json b/chrome/_locales/it/messages.json index 5e857f3b..1fff7a35 100644 --- a/chrome/_locales/it/messages.json +++ b/chrome/_locales/it/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/ja/messages.json b/chrome/_locales/ja/messages.json index ff034eb3..7a2655ce 100644 --- a/chrome/_locales/ja/messages.json +++ b/chrome/_locales/ja/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/ko/messages.json b/chrome/_locales/ko/messages.json index 5bf49033..d01e56b7 100644 --- a/chrome/_locales/ko/messages.json +++ b/chrome/_locales/ko/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "확인" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/ms/messages.json b/chrome/_locales/ms/messages.json index c7ba36c3..14739865 100644 --- a/chrome/_locales/ms/messages.json +++ b/chrome/_locales/ms/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/nl/messages.json b/chrome/_locales/nl/messages.json index f28ce12b..5731e4b2 100644 --- a/chrome/_locales/nl/messages.json +++ b/chrome/_locales/nl/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/pl/messages.json b/chrome/_locales/pl/messages.json index fb949a57..d0799d66 100644 --- a/chrome/_locales/pl/messages.json +++ b/chrome/_locales/pl/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/pt_BR/messages.json b/chrome/_locales/pt_BR/messages.json index 22c76d8d..7d4b8080 100644 --- a/chrome/_locales/pt_BR/messages.json +++ b/chrome/_locales/pt_BR/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "OK" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/ru/messages.json b/chrome/_locales/ru/messages.json index 3b24f601..ce8f042b 100644 --- a/chrome/_locales/ru/messages.json +++ b/chrome/_locales/ru/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "ОК" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/tr/messages.json b/chrome/_locales/tr/messages.json index b7dd51ca..bedc0660 100644 --- a/chrome/_locales/tr/messages.json +++ b/chrome/_locales/tr/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "Tamam" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/zh_CN/messages.json b/chrome/_locales/zh_CN/messages.json index 84d12859..0ea29d73 100644 --- a/chrome/_locales/zh_CN/messages.json +++ b/chrome/_locales/zh_CN/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "确定" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/_locales/zh_TW/messages.json b/chrome/_locales/zh_TW/messages.json index 4a3f1065..050cdbd5 100644 --- a/chrome/_locales/zh_TW/messages.json +++ b/chrome/_locales/zh_TW/messages.json @@ -1180,5 +1180,8 @@ }, "ytslowdown_ok": { "message": "確定" + }, + "options_video_aspect_ratio": { + "message": "Aspect Ratio" } } \ No newline at end of file diff --git a/chrome/player/FastStreamClient.mjs b/chrome/player/FastStreamClient.mjs index 03efea4e..ef5dfe97 100644 --- a/chrome/player/FastStreamClient.mjs +++ b/chrome/player/FastStreamClient.mjs @@ -18,6 +18,7 @@ import {MiniplayerPositions} from './options/defaults/MiniplayerPositions.mjs'; import {SecureMemory} from './modules/SecureMemory.mjs'; import {CSSFilterUtils} from './utils/CSSFilterUtils.mjs'; import {DaltonizerTypes} from './options/defaults/DaltonizerTypes.mjs'; +import {AspectRatios} from './options/defaults/AspectRatios.mjs'; import {Utils} from './utils/Utils.mjs'; import {DefaultToolSettings} from './options/defaults/ToolSettings.mjs'; import {AudioAnalyzer} from './modules/analyzer/AudioAnalyzer.mjs'; @@ -81,6 +82,7 @@ export class FastStreamClient extends EventEmitter { videoDaltonizerType: DaltonizerTypes.NONE, videoDaltonizerStrength: 1, videoZoom: 1, + videoAspectRatio: AspectRatios.AUTO, seekStepSize: 0.2, defaultQuality: 'Auto', toolSettings: Utils.mergeOptions(DefaultToolSettings, {}), @@ -340,6 +342,7 @@ export class FastStreamClient extends EventEmitter { this.options.videoDaltonizerType = options.videoDaltonizerType; this.options.videoDaltonizerStrength = options.videoDaltonizerStrength; this.options.videoZoom = options.videoZoom; + this.options.videoAspectRatio = options.videoAspectRatio || AspectRatios.AUTO; this.options.previewEnabled = options.previewEnabled; this.options.videoDelay = options.videoDelay; document.body.dataset.theme = options.colorTheme; @@ -412,15 +415,20 @@ export class FastStreamClient extends EventEmitter { const filterStr = CSSFilterUtils.getFilterString(this.options); const transformStr = CSSFilterUtils.getTransformString(this.options); + const aspectStyle = CSSFilterUtils.getAspectRatioStyles(this.options); if (this.player) { this.player.getVideo().style.filter = filterStr; this.player.getVideo().style.transform = transformStr; + this.player.getVideo().style.objectFit = aspectStyle.objectFit; + this.player.getVideo().style.aspectRatio = aspectStyle.aspectRatio; } if (this.previewPlayer) { this.previewPlayer.getVideo().style.filter = filterStr; this.previewPlayer.getVideo().style.transform = transformStr; + this.previewPlayer.getVideo().style.objectFit = aspectStyle.objectFit; + this.previewPlayer.getVideo().style.aspectRatio = aspectStyle.aspectRatio; } } diff --git a/chrome/player/options/defaults/AspectRatios.mjs b/chrome/player/options/defaults/AspectRatios.mjs new file mode 100644 index 00000000..f6baa7ec --- /dev/null +++ b/chrome/player/options/defaults/AspectRatios.mjs @@ -0,0 +1,11 @@ +export const AspectRatios = { + AUTO: 'auto', + '16:9': '16:9', + '4:3': '4:3', + '21:9': '21:9', + '1:1': '1:1', + '9:16': '9:16', + '3:2': '3:2', + '16:10': '16:10', + STRETCH: 'stretch', +}; diff --git a/chrome/player/options/defaults/DefaultKeybinds.mjs b/chrome/player/options/defaults/DefaultKeybinds.mjs index f64b3620..7c532f72 100644 --- a/chrome/player/options/defaults/DefaultKeybinds.mjs +++ b/chrome/player/options/defaults/DefaultKeybinds.mjs @@ -38,6 +38,7 @@ export const DefaultKeybinds = { 'ZoomOutVideo': 'None', 'ZoomReset': 'None', 'ToggleVisualFilters': 'KeyQ', + 'CycleAspectRatio': 'None', }; export const KeybindsWithModifiers = [ diff --git a/chrome/player/options/defaults/DefaultOptions.mjs b/chrome/player/options/defaults/DefaultOptions.mjs index d2f993a6..de4a25ac 100644 --- a/chrome/player/options/defaults/DefaultOptions.mjs +++ b/chrome/player/options/defaults/DefaultOptions.mjs @@ -1,5 +1,6 @@ import {YoutubeClients} from '../../enums/YoutubeClients.mjs'; import {EnvUtils} from '../../utils/EnvUtils.mjs'; +import {AspectRatios} from './AspectRatios.mjs'; import {ClickActions} from './ClickActions.mjs'; import {ColorThemes} from './ColorThemes.mjs'; import {DaltonizerTypes} from './DaltonizerTypes.mjs'; @@ -35,6 +36,7 @@ export const DefaultOptions = { videoDaltonizerType: DaltonizerTypes.NONE, videoDaltonizerStrength: 1, videoZoom: 1, + videoAspectRatio: AspectRatios.AUTO, maxSpeed: -1, maxVideoSize: 5000000000, // 5GB max size seekStepSize: 2, diff --git a/chrome/player/options/index.html b/chrome/player/options/index.html index 6624b66e..213d2d9b 100644 --- a/chrome/player/options/index.html +++ b/chrome/player/options/index.html @@ -126,6 +126,11 @@

data-i18n-label="options_video_hue"> +
+
+
+
+
diff --git a/chrome/player/options/options.mjs b/chrome/player/options/options.mjs index a480213e..7541a8d6 100644 --- a/chrome/player/options/options.mjs +++ b/chrome/player/options/options.mjs @@ -14,6 +14,7 @@ import {VisChangeActions} from './defaults/VisChangeActions.mjs'; import {MiniplayerPositions} from './defaults/MiniplayerPositions.mjs'; import {DefaultSubtitlesSettings} from './defaults/DefaultSubtitlesSettings.mjs'; import {DaltonizerTypes} from './defaults/DaltonizerTypes.mjs'; +import {AspectRatios} from './defaults/AspectRatios.mjs'; import {DefaultToolSettings} from './defaults/ToolSettings.mjs'; import {DefaultQualities} from './defaults/DefaultQualities.mjs'; import {ColorThemes} from './defaults/ColorThemes.mjs'; @@ -43,6 +44,7 @@ const showWhenMiniSelected = document.getElementById('showWhenMiniSelected'); const storeProgress = document.getElementById('storeprogress'); const miniSize = document.getElementById('minisize'); const miniPos = document.getElementById('minipos'); +const videoAspectRatio = document.getElementById('videoAspectRatio'); const daltonizerType = document.getElementById('daltonizerType'); const daltonizerStrength = document.getElementById('daltonizerStrength'); const previewEnabled = document.getElementById('previewenabled'); @@ -109,6 +111,7 @@ async function loadOptions(newOptions) { maxdownloaders.value = Options.maximumDownloaders; ytPlayerID.value = Options.youtubePlayerID; + setSelectMenuValue(videoAspectRatio, Options.videoAspectRatio); setSelectMenuValue(daltonizerType, Options.videoDaltonizerType); setSelectMenuValue(clickAction, Options.singleClickAction); setSelectMenuValue(dblclickAction, Options.doubleClickAction); @@ -167,7 +170,16 @@ function createSelectMenu(container, options, selected, localPrefix, callback) { for (const option of options) { const optionElement = document.createElement('option'); optionElement.value = option; - optionElement.textContent = localPrefix !== null ? Localize.getMessage(localPrefix + '_' + option) : option; + let label = option; + if (localPrefix !== null) { + const msgKey = localPrefix + '_' + option; + const msg = Localize.getMessage(msgKey); + label = msg !== msgKey ? msg : option; + } else { + if (option === 'auto') label = 'Auto (Default)'; + else if (option === 'stretch') label = 'Stretch'; + } + optionElement.textContent = label; if (option === selected) { optionElement.selected = true; } @@ -185,6 +197,11 @@ function setSelectMenuValue(container, value) { select.value = value; } +createSelectMenu(videoAspectRatio, Object.values(AspectRatios), Options.videoAspectRatio, null, (e) => { + Options.videoAspectRatio = e.target.value; + optionChanged(); +}); + createSelectMenu(daltonizerType, Object.values(DaltonizerTypes), Options.videoDaltonizerType, 'options_video_daltonizer', (e) => { Options.videoDaltonizerType = e.target.value; if (Options.videoDaltonizerType === DaltonizerTypes.NONE) { diff --git a/chrome/player/ui/KeybindManager.mjs b/chrome/player/ui/KeybindManager.mjs index 4e9ffb8f..1d6624a7 100644 --- a/chrome/player/ui/KeybindManager.mjs +++ b/chrome/player/ui/KeybindManager.mjs @@ -3,6 +3,7 @@ import {EventEmitter} from '../modules/eventemitter.mjs'; import {WebUtils} from '../utils/WebUtils.mjs'; import {DOMElements} from './DOMElements.mjs'; import {Utils} from '../utils/Utils.mjs'; +import {AspectRatios} from '../options/defaults/AspectRatios.mjs'; export class KeybindManager extends EventEmitter { constructor(client) { @@ -210,6 +211,15 @@ export class KeybindManager extends EventEmitter { this.client.updateCSSFilters(); }); + this.on('CycleAspectRatio', (e) => { + const options = this.client.options; + const ratios = Object.values(AspectRatios); + const currentIndex = ratios.indexOf(options.videoAspectRatio); + const nextIndex = (currentIndex + 1) % ratios.length; + options.videoAspectRatio = ratios[nextIndex]; + this.client.updateCSSFilters(); + }); + this.on('WindowedFullscreen', (e) => { this.client.interfaceController.toggleWindowedFullscreen(); }); diff --git a/chrome/player/utils/CSSFilterUtils.mjs b/chrome/player/utils/CSSFilterUtils.mjs index 9f07b358..a8e32461 100644 --- a/chrome/player/utils/CSSFilterUtils.mjs +++ b/chrome/player/utils/CSSFilterUtils.mjs @@ -85,4 +85,22 @@ export class CSSFilterUtils { return transforms.join(' '); } + + /** + * Generates aspect ratio styles based on video options. + * @param {Object} options - Video options containing videoAspectRatio. + * @return {{objectFit: string, aspectRatio: string}} + */ + static getAspectRatioStyles(options) { + if (!options || !options.videoAspectRatio || options.videoAspectRatio === 'auto') { + return {objectFit: '', aspectRatio: ''}; + } + if (options.videoAspectRatio === 'stretch') { + return {objectFit: 'fill', aspectRatio: ''}; + } + return { + objectFit: 'fill', + aspectRatio: options.videoAspectRatio.replace(':', '/'), + }; + } } diff --git a/combined-locales.json b/combined-locales.json index 578f682b..4adab48b 100644 --- a/combined-locales.json +++ b/combined-locales.json @@ -5399,6 +5399,24 @@ "zh_CN": "色相旋转", "zh_TW": "色相旋轉" }, + "options_video_aspect_ratio": { + "en": "Aspect Ratio", + "de": "Aspect Ratio", + "es": "Aspect Ratio", + "fr": "Aspect Ratio", + "id": "Aspect Ratio", + "it": "Aspect Ratio", + "ja": "Aspect Ratio", + "ko": "Aspect Ratio", + "ms": "Aspect Ratio", + "nl": "Aspect Ratio", + "pl": "Aspect Ratio", + "pt_BR": "Aspect Ratio", + "ru": "Aspect Ratio", + "tr": "Aspect Ratio", + "zh_CN": "Aspect Ratio", + "zh_TW": "Aspect Ratio" + }, "options_video_daltonizer_type": { "en": "Color blindness correction", "de": "Korrekturen von Farbenblindheit",