-
-
Notifications
You must be signed in to change notification settings - Fork 228
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(subtitles): settings & custom element for external subtitles #2360
Merged
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
6f66f06
feat(subtitles): settings & custom element for external subtitles
seanmcbroom 9ddb61d
fix: change subtitle stroke css properties to work with chromium brow…
seanmcbroom 56f3212
fix(settings): refactor subtitle page to await loading and general le…
seanmcbroom 3a6a4e4
refactor(subtitles): use computed value for current subtitle variable
seanmcbroom b8ad765
fix(locale): general fixes regarding subtitles
seanmcbroom 2b3e52e
refactor(subtitles): make SSA file parsing more reliable and add mult…
seanmcbroom b48f2d8
refactor: reuse SubtitleTrack element in subtitle settings page
seanmcbroom 6d2cda6
refactor: update replaceTags method to replace entire tags
seanmcbroom c723dc1
fix: general tag formatting changes for rendering as html
seanmcbroom a048aeb
refactor: use uno css for subtitle track styles
seanmcbroom 6b1cbc0
refactor: check if useFullscreen is supported to apply custom subtitl…
seanmcbroom 2c44ebc
refactor: add logic for basic sass subtitles
seanmcbroom 9ded2d8
refactor: link fontsize & position sliders directly to client settings
seanmcbroom 114617f
feat(FontSelector): refactor font selector logic and move to component
seanmcbroom c875ff2
feat(font-settings): query font from document css
seanmcbroom eba345c
style: automatic eslint fix
ferferga bea50ad
fix: handle cases where ssa sub dialogue include newline character
seanmcbroom e7193fe
refactor(player-element): cleanup & standardize applying subtitles
seanmcbroom 99b4827
refactor(playback-manager): reduce shared code
seanmcbroom fd0398d
refactor: fix sonarcloud issues
seanmcbroom 22f025c
feat: implement useFont compostable
seanmcbroom 20e5480
refactor: extract all subtitle setting functionality to new store
seanmcbroom 49c313c
refactor: simplify subtitle store
ferferga 89a5dbc
refactor&fix: font
ferferga c98ae0e
feat: don't apply subtitle settings if customization is disabled
ferferga fb4c56a
refactor: extract all logic to webworker
ferferga bdacaff
perf: optimize subtitle finding
ferferga 1e8d0bc
refactor(store): skip syncing font family
ferferga ec3d5e2
refactor: address review comments
ferferga 74ab7d3
refactor: fix displaying subtitle tracks
seanmcbroom aa797ae
refactor: improvements and fixes for jassub rendering
seanmcbroom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
seanmcbroom marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<template> | ||
<div | ||
class="uno-absolute uno-bottom-0 uno-left-0 uno-w-full uno-text-center"> | ||
<span | ||
class="uno-inline-block uno-pb-10px uno-color-white" | ||
:class="{ 'stroked': subtitleSettings.state.stroke }" | ||
:style="subtitleStyle"> | ||
<template v-if="preview"> | ||
{{ $t('subtitlePreviewText') }} | ||
</template> | ||
<JSafeHtml | ||
v-else-if="!isNil(currentSubtitle?.sub)" | ||
:html="currentSubtitle.sub.text" /> | ||
</span> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computed, type StyleValue } from 'vue'; | ||
import { subtitleSettings } from '@/store/client-settings/subtitle-settings'; | ||
import { DEFAULT_TYPOGRAPHY } from '@/store'; | ||
import { playerElement } from '@/store/player-element'; | ||
import { isNil } from '@/utils/validation'; | ||
import type { ParsedSubtitleTrack, Dialogue } from '@/plugins/workers/generic/subtitles'; | ||
import { playbackManager } from '@/store/playback-manager'; | ||
|
||
const { preview } = defineProps<{ | ||
/** | ||
* Whether the subtitle track is in preview mode. | ||
*/ | ||
preview?: boolean; | ||
}>(); | ||
|
||
/** | ||
* Update the current subtitle based on the current time of the media element. | ||
* | ||
* Loops in the first run (we can't assume that the first run will appear at index 0, | ||
* since the user can seek to any position) when 'previous' is undefined and then relies in previous | ||
* to find the next one | ||
*/ | ||
const predicate = (d: Dialogue) => d.start <= playbackManager.currentTime && d.end >= playbackManager.currentTime; | ||
const findSubtitle = (dialogue: ParsedSubtitleTrack['dialogue'], start = 0) => { | ||
const index = dialogue.slice(start).findIndex(d => predicate(d)); | ||
|
||
return index === -1 ? undefined : index; | ||
}; | ||
|
||
const dialogue = computed(() => playerElement.currentExternalSubtitleTrack?.parsed?.dialogue); | ||
const currentSubtitle = computed<{ index: number; sub?: Dialogue | undefined } | undefined>((previous) => { | ||
if (!isNil(dialogue.value)) { | ||
const hasPrevious = !isNil(previous); | ||
const nextIndex = hasPrevious ? previous.index + 1 : 0; | ||
const isNext = hasPrevious && predicate(dialogue.value[nextIndex]); | ||
const isCurrent = hasPrevious && predicate(dialogue.value[previous.index]); | ||
|
||
if (isCurrent) { | ||
return previous; | ||
} else { | ||
const newIndex = isNext ? nextIndex : findSubtitle(dialogue.value, nextIndex); | ||
|
||
if (!isNil(newIndex)) { | ||
return { index: newIndex, sub: dialogue.value[newIndex] }; | ||
} else if (hasPrevious) { | ||
return { index: previous.index }; | ||
} | ||
} | ||
} | ||
}); | ||
|
||
const fontFamily = computed(() => { | ||
if (subtitleSettings.state.fontFamily === 'default') { | ||
return DEFAULT_TYPOGRAPHY; | ||
} else if (subtitleSettings.state.fontFamily === 'system') { | ||
return 'system-ui'; | ||
} else if (subtitleSettings.state.fontFamily !== 'auto') { | ||
return subtitleSettings.state.fontFamily; | ||
} | ||
}); | ||
|
||
/** | ||
* Computed style for subtitle text element | ||
* reactive to client subtitle appearance settings | ||
*/ | ||
const subtitleStyle = computed<StyleValue>(() => { | ||
const subtitleAppearance = subtitleSettings.state; | ||
|
||
return { | ||
fontSize: `${subtitleAppearance.fontSize}em`, | ||
marginBottom: `${subtitleAppearance.positionFromBottom}vh`, | ||
backgroundColor: subtitleAppearance.backdrop ? 'rgba(0, 0, 0, 0.5)' : 'transparent', | ||
/** | ||
* Unwrap font family and stroke style if stroke is enabled | ||
*/ | ||
...(fontFamily.value && { | ||
fontFamily: `${fontFamily.value} !important` | ||
}) | ||
}; | ||
}); | ||
</script> | ||
|
||
<style scoped> | ||
.stroked { | ||
-webkit-text-stroke: 7px black; | ||
text-shadow: 2px 2px 15px black; | ||
paint-order: stroke fill; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
<template> | ||
<VAlert | ||
v-if="!isQueryLocalFontsSupported" | ||
class="uno-mb-5" | ||
color="warning" | ||
icon="$warning"> | ||
{{ $t('queryLocalFontsNotSupportedWarning') }} | ||
<br> | ||
<a | ||
class="uno-font-bold" | ||
href="https://caniuse.com/mdn-api_window_querylocalfonts" | ||
target="_blank" | ||
rel="noopener"> | ||
{{ $t('learnMore') }} | ||
</a> | ||
</VAlert> | ||
|
||
<VAlert | ||
v-else-if="!fontAccess" | ||
class="uno-mb-5" | ||
color="warning" | ||
icon="$warning"> | ||
{{ $t('localFontsPermissionWarning') }} | ||
<br> | ||
<a | ||
class="uno-font-bold" | ||
href="https://support.google.com/chrome/answer/114662?hl=en&co=GENIE.Platform=Desktop" | ||
target="_blank" | ||
rel="noopener"> | ||
{{ $t('enablePermission') }} | ||
</a> | ||
<a | ||
class="uno-font-bold" | ||
@click="askForPermission"> | ||
{{ $t('askAgain') }} | ||
</a> | ||
</VAlert> | ||
|
||
<VSelect | ||
v-model="_model" | ||
v-bind="$attrs" | ||
:items="selection" | ||
:disabled="!isQueryLocalFontsSupported || !fontAccess || disabled" /> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { computedAsync, usePermission, useSupported } from '@vueuse/core'; | ||
import { computed } from 'vue'; | ||
import { useI18n } from 'vue-i18n'; | ||
import { clientSettings } from '@/store/client-settings'; | ||
import { DEFAULT_TYPOGRAPHY } from '@/store'; | ||
|
||
const { appWide } = defineProps<{ | ||
/** | ||
* If this font selector is used for selecting the typography for the whole app | ||
*/ | ||
appWide?: boolean; | ||
disabled?: boolean; | ||
}>(); | ||
|
||
const model = defineModel<string | undefined>(); | ||
const { t } = useI18n(); | ||
|
||
const { query: permissionQuery, isSupported, state: fontPermission } = usePermission('local-fonts', { controls: true }); | ||
const fontAccess = computed(() => fontPermission.value === 'granted'); | ||
const isQueryLocalFontsSupported = useSupported(() => isSupported.value && 'queryLocalFonts' in window); | ||
const askForPermission = async () => isQueryLocalFontsSupported.value | ||
? Promise.all([permissionQuery, window.queryLocalFonts]) | ||
: undefined; | ||
|
||
/** | ||
* Edge at least doesn't allow for querying the permission directly using navigator.permission, | ||
* only after querying the fonts, so we perform the query regardless at the beginning. | ||
*/ | ||
const fontList = computedAsync(async () => { | ||
const res: string[] = []; | ||
|
||
if (fontAccess.value || isQueryLocalFontsSupported.value) { | ||
const set = new Set<string>((await window.queryLocalFonts()).map((font: FontFace) => font.family)); | ||
|
||
/** | ||
* Removes the current selected tpography (in case it's not the default one) | ||
*/ | ||
set.delete(clientSettings.typography); | ||
res.push(...set); | ||
} | ||
|
||
return res; | ||
}, []); | ||
|
||
const selection = computed(() => { | ||
const res = [ | ||
{ | ||
title: t('appDefaultTypography', { value: DEFAULT_TYPOGRAPHY }), | ||
value: 'default' | ||
}, | ||
{ | ||
title: t('systemTypography'), | ||
value: 'system' | ||
}, ...fontList.value.map(f => ({ | ||
title: f, | ||
value: f | ||
}))]; | ||
|
||
if (!appWide && !['system', 'default'].includes(clientSettings.typography)) { | ||
res.unshift( | ||
{ | ||
title: t('currentAppTypography', { | ||
value: clientSettings.typography | ||
}), | ||
value: clientSettings.typography | ||
} | ||
); | ||
} | ||
|
||
return res; | ||
}); | ||
|
||
const _model = computed({ | ||
get() { | ||
if (appWide) { | ||
return clientSettings.typography; | ||
} | ||
|
||
return model.value; | ||
}, | ||
set(newVal) { | ||
if (appWide && newVal) { | ||
clientSettings.typography = newVal; | ||
} | ||
|
||
model.value = newVal; | ||
} | ||
}); | ||
</script> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously we were showing the browser's tracks alongisde our custom ones. I guess this is not the wanted behaviour, right? I also added a new toggle in the settings to toggle the subtitle customization (that's what configures the
subtitleSettings.state.enabled
variableThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree the only time it should show the custom track is when the subtitle customization is enabled/available, otherwise it should show the default track element.