diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index 5511dc535..7912bee00 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -56,6 +56,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult { language slideshowDelay handyKey + funscriptOffset } fragment ConfigDLNAData on ConfigDLNAResult { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index cec708d16..63fd730c5 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -200,6 +200,8 @@ input ConfigInterfaceInput { slideshowDelay: Int """Handy Connection Key""" handyKey: String + """Funscript Time Offset""" + funscriptOffset: Int } type ConfigInterfaceResult { @@ -226,6 +228,8 @@ type ConfigInterfaceResult { slideshowDelay: Int """Handy Connection Key""" handyKey: String + """Funscript Time Offset""" + funscriptOffset: Int } input ConfigDLNAInput { diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index a6083022f..4eb477731 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -260,6 +260,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models. c.Set(config.HandyKey, *input.HandyKey) } + if input.FunscriptOffset != nil { + c.Set(config.FunscriptOffset, *input.FunscriptOffset) + } + if err := c.Write(); err != nil { return makeConfigInterfaceResult(), err } diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index c8897c882..27aaac925 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -109,6 +109,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult { language := config.GetLanguage() slideshowDelay := config.GetSlideshowDelay() handyKey := config.GetHandyKey() + scriptOffset := config.GetFunscriptOffset() return &models.ConfigInterfaceResult{ MenuItems: menuItems, @@ -123,6 +124,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult { Language: &language, SlideshowDelay: &slideshowDelay, HandyKey: &handyKey, + FunscriptOffset: &scriptOffset, } } diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index eb1db30dc..c97f5ec67 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -132,6 +132,7 @@ const CSSEnabled = "cssEnabled" const WallPlayback = "wall_playback" const SlideshowDelay = "slideshow_delay" const HandyKey = "handy_key" +const FunscriptOffset = "funscript_offset" // DLNA options const DLNAServerName = "dlna.server_name" @@ -798,6 +799,11 @@ func (i *Instance) GetHandyKey() string { return viper.GetString(HandyKey) } +func (i *Instance) GetFunscriptOffset() int { + viper.SetDefault(FunscriptOffset, 0) + return viper.GetInt(FunscriptOffset) +} + // GetDLNAServerName returns the visible name of the DLNA server. If empty, // "stash" will be used. func (i *Instance) GetDLNAServerName() string { diff --git a/ui/v2.5/src/components/Changelog/versions/v090.md b/ui/v2.5/src/components/Changelog/versions/v090.md index 62219fc91..dfda34f86 100644 --- a/ui/v2.5/src/components/Changelog/versions/v090.md +++ b/ui/v2.5/src/components/Changelog/versions/v090.md @@ -1,4 +1,5 @@ ### ✨ New Features +* Support setting a fixed funscript offset/delay. ([#1573](https://github.com/stashapp/stash/pull/1573)) * Added sort by options for image and gallery count for performers. ([#1671](https://github.com/stashapp/stash/pull/1671)) * Added sort by options for date, duration and rating for movies. ([#1663](https://github.com/stashapp/stash/pull/1663)) * Allow saving query page zoom level in saved and default filters. ([#1636](https://github.com/stashapp/stash/pull/1636)) @@ -12,7 +13,7 @@ ### 🎨 Improvements * Move Play Selected Scenes, and Add/Remove Gallery Image buttons to button toolbar. ([#1673](https://github.com/stashapp/stash/pull/1673)) -* Add image and gallery counts to tag list view. ([#1672](https://github.com/stashapp/stash/pull/1672)) +* Added image and gallery counts to tag list view. ([#1672](https://github.com/stashapp/stash/pull/1672)) * Prompt when leaving gallery and image edit pages with unsaved changes. ([#1654](https://github.com/stashapp/stash/pull/1654), [#1669](https://github.com/stashapp/stash/pull/1669)) * Show largest duplicates first in scene duplicate checker. ([#1639](https://github.com/stashapp/stash/pull/1639)) * Added checkboxes to scene list view. ([#1642](https://github.com/stashapp/stash/pull/1642)) diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index 62460add5..77ef81552 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -54,7 +54,10 @@ export class ScenePlayerImpl extends React.Component< this.state = { scrubberPosition: 0, config: this.makeJWPlayerConfig(props.scene), - interactiveClient: new Interactive(this.props.config?.handyKey || ""), + interactiveClient: new Interactive( + this.props.config?.handyKey || "", + this.props.config?.funscriptOffset || 0 + ), }; // Default back to Direct Streaming diff --git a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx index 779a748f7..e464ae610 100644 --- a/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsInterfacePanel/SettingsInterfacePanel.tsx @@ -37,6 +37,7 @@ export const SettingsInterfacePanel: React.FC = () => { const [cssEnabled, setCSSEnabled] = useState(false); const [language, setLanguage] = useState("en"); const [handyKey, setHandyKey] = useState(); + const [funscriptOffset, setFunscriptOffset] = useState(0); const [updateInterfaceConfig] = useConfigureInterface({ menuItems: menuItemIds, @@ -51,6 +52,7 @@ export const SettingsInterfacePanel: React.FC = () => { language, slideshowDelay, handyKey, + funscriptOffset, }); useEffect(() => { @@ -67,6 +69,7 @@ export const SettingsInterfacePanel: React.FC = () => { setLanguage(iCfg?.language ?? "en-US"); setSlideshowDelay(iCfg?.slideshowDelay ?? 5000); setHandyKey(iCfg?.handyKey ?? ""); + setFunscriptOffset(iCfg?.funscriptOffset ?? 0); }, [config]); async function onSave() { @@ -281,7 +284,9 @@ export const SettingsInterfacePanel: React.FC = () => { -
{intl.formatMessage({ id: "config.ui.handy_connection_key" })}
+
+ {intl.formatMessage({ id: "config.ui.handy_connection_key.heading" })} +
{ }} /> - {intl.formatMessage({ id: "config.ui.handy_connection_key_desc" })} + {intl.formatMessage({ + id: "config.ui.handy_connection_key.description", + })} + +
+ +
+ {intl.formatMessage({ id: "config.ui.funscript_offset.heading" })} +
+ ) => { + setFunscriptOffset(Number.parseInt(e.currentTarget.value, 10)); + }} + /> + + {intl.formatMessage({ id: "config.ui.funscript_offset.description" })}
diff --git a/ui/v2.5/src/locales/de-DE.json b/ui/v2.5/src/locales/de-DE.json index e22e410cf..32bd74623 100644 --- a/ui/v2.5/src/locales/de-DE.json +++ b/ui/v2.5/src/locales/de-DE.json @@ -308,8 +308,10 @@ "heading": "Benutzerdefinierte CSS", "option_label": "Benutzerdefiniertes CSS aktiviert" }, - "handy_connection_key": "Handy Verbindungsschlüssel", - "handy_connection_key_desc": "Handy Verbindungsschlüssel für interaktive Szenen.", + "handy_connection_key": { + "description": "Handy Verbindungsschlüssel für interaktive Szenen.", + "heading": "Handy Verbindungsschlüssel" + }, "language": { "heading": "Sprache" }, diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json index 2ffd8cc45..34b58e700 100644 --- a/ui/v2.5/src/locales/en-GB.json +++ b/ui/v2.5/src/locales/en-GB.json @@ -315,8 +315,14 @@ "heading": "Custom CSS", "option_label": "Custom CSS enabled" }, - "handy_connection_key": "Handy Connection Key", - "handy_connection_key_desc": "Handy connection key to use for interactive scenes.", + "handy_connection_key": { + "description": "Handy connection key to use for interactive scenes.", + "heading": "Handy Connection Key" + }, + "funscript_offset": { + "description": "Time offset in milliseconds for interactive scripts playback.", + "heading": "Funscript Offset (ms)" + }, "language": { "heading": "Language" }, diff --git a/ui/v2.5/src/locales/pt-BR.json b/ui/v2.5/src/locales/pt-BR.json index c56139dcd..64ce8e204 100644 --- a/ui/v2.5/src/locales/pt-BR.json +++ b/ui/v2.5/src/locales/pt-BR.json @@ -308,8 +308,10 @@ "heading": "CSS customizado", "option_label": "CSS customizado habilitado" }, - "handy_connection_key": "Chave de conexão", - "handy_connection_key_desc": "Chave de conexão para usar em cenas interativas.", + "handy_connection_key": { + "description": "Chave de conexão para usar em cenas interativas.", + "heading": "Chave de conexão" + }, "language": { "heading": "Idioma" }, diff --git a/ui/v2.5/src/locales/zh-CN.json b/ui/v2.5/src/locales/zh-CN.json index 5182f4b81..c65d6810e 100644 --- a/ui/v2.5/src/locales/zh-CN.json +++ b/ui/v2.5/src/locales/zh-CN.json @@ -308,8 +308,10 @@ "heading": "自定义样式", "option_label": "自定义样式已启用" }, - "handy_connection_key": "快速连接密钥", - "handy_connection_key_desc": "用于互动场景的快速连接密钥", + "handy_connection_key": { + "description": "用于互动场景的快速连接密钥", + "heading": "快速连接密钥" + }, "language": { "heading": "语言" }, diff --git a/ui/v2.5/src/locales/zh-TW.json b/ui/v2.5/src/locales/zh-TW.json index 24add9259..07f443c52 100644 --- a/ui/v2.5/src/locales/zh-TW.json +++ b/ui/v2.5/src/locales/zh-TW.json @@ -310,8 +310,10 @@ "heading": "自定義 CSS", "option_label": "啟用自定義 CSS" }, - "handy_connection_key": "Handy 連線金鑰", - "handy_connection_key_desc": "播放支援互動性的短片時所用的 Handy 連線金鑰。", + "handy_connection_key": { + "description": "播放支援互動性的短片時所用的 Handy 連線金鑰。", + "heading": "Handy 連線金鑰" + }, "language": { "heading": "語言" }, diff --git a/ui/v2.5/src/utils/interactive.ts b/ui/v2.5/src/utils/interactive.ts index b6887e372..4dd01ec18 100644 --- a/ui/v2.5/src/utils/interactive.ts +++ b/ui/v2.5/src/utils/interactive.ts @@ -26,11 +26,13 @@ function convertFunscriptToCSV(funscript: IFunscript) { export class Interactive { private _connected: boolean; private _playing: boolean; + private _scriptOffset: number; private _handy: Handy; - constructor(handyKey: string) { + constructor(handyKey: string, scriptOffset: number) { this._handy = new Handy(); this._handy.connectionKey = handyKey; + this._scriptOffset = scriptOffset; this._connected = false; this._playing = false; } @@ -76,7 +78,7 @@ export class Interactive { return; } this._playing = await this._handy - .syncPlay(true, Math.round(position * 1000)) + .syncPlay(true, Math.round(position * 1000 + this._scriptOffset)) .then(() => true); }