Add script offset / delay to Handy support. (#1573)

* Add script offset / delay to Handy support.

Further work on  #1376.

Offsets are added to the current video position, so a positive value leads to earlier motion.  (The most common setting.)

This is needed because most script times have a consistent delay when compared to the video. (Delay from the API calls to the server should be handled by the server offset calculation.)

* Rename scriptOffset to funscriptOffset
* Correct localisation keys

Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
This commit is contained in:
UnluckyChemical765
2021-08-25 18:50:02 -07:00
committed by GitHub
parent 50217f6318
commit 45a9aabdaf
14 changed files with 76 additions and 16 deletions

View File

@@ -56,6 +56,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
language language
slideshowDelay slideshowDelay
handyKey handyKey
funscriptOffset
} }
fragment ConfigDLNAData on ConfigDLNAResult { fragment ConfigDLNAData on ConfigDLNAResult {

View File

@@ -200,6 +200,8 @@ input ConfigInterfaceInput {
slideshowDelay: Int slideshowDelay: Int
"""Handy Connection Key""" """Handy Connection Key"""
handyKey: String handyKey: String
"""Funscript Time Offset"""
funscriptOffset: Int
} }
type ConfigInterfaceResult { type ConfigInterfaceResult {
@@ -226,6 +228,8 @@ type ConfigInterfaceResult {
slideshowDelay: Int slideshowDelay: Int
"""Handy Connection Key""" """Handy Connection Key"""
handyKey: String handyKey: String
"""Funscript Time Offset"""
funscriptOffset: Int
} }
input ConfigDLNAInput { input ConfigDLNAInput {

View File

@@ -260,6 +260,10 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input models.
c.Set(config.HandyKey, *input.HandyKey) c.Set(config.HandyKey, *input.HandyKey)
} }
if input.FunscriptOffset != nil {
c.Set(config.FunscriptOffset, *input.FunscriptOffset)
}
if err := c.Write(); err != nil { if err := c.Write(); err != nil {
return makeConfigInterfaceResult(), err return makeConfigInterfaceResult(), err
} }

View File

@@ -109,6 +109,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
language := config.GetLanguage() language := config.GetLanguage()
slideshowDelay := config.GetSlideshowDelay() slideshowDelay := config.GetSlideshowDelay()
handyKey := config.GetHandyKey() handyKey := config.GetHandyKey()
scriptOffset := config.GetFunscriptOffset()
return &models.ConfigInterfaceResult{ return &models.ConfigInterfaceResult{
MenuItems: menuItems, MenuItems: menuItems,
@@ -123,6 +124,7 @@ func makeConfigInterfaceResult() *models.ConfigInterfaceResult {
Language: &language, Language: &language,
SlideshowDelay: &slideshowDelay, SlideshowDelay: &slideshowDelay,
HandyKey: &handyKey, HandyKey: &handyKey,
FunscriptOffset: &scriptOffset,
} }
} }

View File

@@ -132,6 +132,7 @@ const CSSEnabled = "cssEnabled"
const WallPlayback = "wall_playback" const WallPlayback = "wall_playback"
const SlideshowDelay = "slideshow_delay" const SlideshowDelay = "slideshow_delay"
const HandyKey = "handy_key" const HandyKey = "handy_key"
const FunscriptOffset = "funscript_offset"
// DLNA options // DLNA options
const DLNAServerName = "dlna.server_name" const DLNAServerName = "dlna.server_name"
@@ -798,6 +799,11 @@ func (i *Instance) GetHandyKey() string {
return viper.GetString(HandyKey) 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, // GetDLNAServerName returns the visible name of the DLNA server. If empty,
// "stash" will be used. // "stash" will be used.
func (i *Instance) GetDLNAServerName() string { func (i *Instance) GetDLNAServerName() string {

View File

@@ -1,4 +1,5 @@
### ✨ New Features ### ✨ 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 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)) * 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)) * Allow saving query page zoom level in saved and default filters. ([#1636](https://github.com/stashapp/stash/pull/1636))
@@ -12,7 +13,7 @@
### 🎨 Improvements ### 🎨 Improvements
* Move Play Selected Scenes, and Add/Remove Gallery Image buttons to button toolbar. ([#1673](https://github.com/stashapp/stash/pull/1673)) * 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)) * 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)) * 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)) * Added checkboxes to scene list view. ([#1642](https://github.com/stashapp/stash/pull/1642))

View File

@@ -54,7 +54,10 @@ export class ScenePlayerImpl extends React.Component<
this.state = { this.state = {
scrubberPosition: 0, scrubberPosition: 0,
config: this.makeJWPlayerConfig(props.scene), 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 // Default back to Direct Streaming

View File

@@ -37,6 +37,7 @@ export const SettingsInterfacePanel: React.FC = () => {
const [cssEnabled, setCSSEnabled] = useState<boolean>(false); const [cssEnabled, setCSSEnabled] = useState<boolean>(false);
const [language, setLanguage] = useState<string>("en"); const [language, setLanguage] = useState<string>("en");
const [handyKey, setHandyKey] = useState<string>(); const [handyKey, setHandyKey] = useState<string>();
const [funscriptOffset, setFunscriptOffset] = useState<number>(0);
const [updateInterfaceConfig] = useConfigureInterface({ const [updateInterfaceConfig] = useConfigureInterface({
menuItems: menuItemIds, menuItems: menuItemIds,
@@ -51,6 +52,7 @@ export const SettingsInterfacePanel: React.FC = () => {
language, language,
slideshowDelay, slideshowDelay,
handyKey, handyKey,
funscriptOffset,
}); });
useEffect(() => { useEffect(() => {
@@ -67,6 +69,7 @@ export const SettingsInterfacePanel: React.FC = () => {
setLanguage(iCfg?.language ?? "en-US"); setLanguage(iCfg?.language ?? "en-US");
setSlideshowDelay(iCfg?.slideshowDelay ?? 5000); setSlideshowDelay(iCfg?.slideshowDelay ?? 5000);
setHandyKey(iCfg?.handyKey ?? ""); setHandyKey(iCfg?.handyKey ?? "");
setFunscriptOffset(iCfg?.funscriptOffset ?? 0);
}, [config]); }, [config]);
async function onSave() { async function onSave() {
@@ -281,7 +284,9 @@ export const SettingsInterfacePanel: React.FC = () => {
</Form.Group> </Form.Group>
<Form.Group> <Form.Group>
<h5>{intl.formatMessage({ id: "config.ui.handy_connection_key" })}</h5> <h5>
{intl.formatMessage({ id: "config.ui.handy_connection_key.heading" })}
</h5>
<Form.Control <Form.Control
className="col col-sm-6 text-input" className="col col-sm-6 text-input"
value={handyKey} value={handyKey}
@@ -290,7 +295,25 @@ export const SettingsInterfacePanel: React.FC = () => {
}} }}
/> />
<Form.Text className="text-muted"> <Form.Text className="text-muted">
{intl.formatMessage({ id: "config.ui.handy_connection_key_desc" })} {intl.formatMessage({
id: "config.ui.handy_connection_key.description",
})}
</Form.Text>
</Form.Group>
<Form.Group>
<h5>
{intl.formatMessage({ id: "config.ui.funscript_offset.heading" })}
</h5>
<Form.Control
className="col col-sm-6 text-input"
type="number"
value={funscriptOffset}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setFunscriptOffset(Number.parseInt(e.currentTarget.value, 10));
}}
/>
<Form.Text className="text-muted">
{intl.formatMessage({ id: "config.ui.funscript_offset.description" })}
</Form.Text> </Form.Text>
</Form.Group> </Form.Group>

View File

@@ -308,8 +308,10 @@
"heading": "Benutzerdefinierte CSS", "heading": "Benutzerdefinierte CSS",
"option_label": "Benutzerdefiniertes CSS aktiviert" "option_label": "Benutzerdefiniertes CSS aktiviert"
}, },
"handy_connection_key": "Handy Verbindungsschlüssel", "handy_connection_key": {
"handy_connection_key_desc": "Handy Verbindungsschlüssel für interaktive Szenen.", "description": "Handy Verbindungsschlüssel für interaktive Szenen.",
"heading": "Handy Verbindungsschlüssel"
},
"language": { "language": {
"heading": "Sprache" "heading": "Sprache"
}, },

View File

@@ -315,8 +315,14 @@
"heading": "Custom CSS", "heading": "Custom CSS",
"option_label": "Custom CSS enabled" "option_label": "Custom CSS enabled"
}, },
"handy_connection_key": "Handy Connection Key", "handy_connection_key": {
"handy_connection_key_desc": "Handy connection key to use for interactive scenes.", "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": { "language": {
"heading": "Language" "heading": "Language"
}, },

View File

@@ -308,8 +308,10 @@
"heading": "CSS customizado", "heading": "CSS customizado",
"option_label": "CSS customizado habilitado" "option_label": "CSS customizado habilitado"
}, },
"handy_connection_key": "Chave de conexão", "handy_connection_key": {
"handy_connection_key_desc": "Chave de conexão para usar em cenas interativas.", "description": "Chave de conexão para usar em cenas interativas.",
"heading": "Chave de conexão"
},
"language": { "language": {
"heading": "Idioma" "heading": "Idioma"
}, },

View File

@@ -308,8 +308,10 @@
"heading": "自定义样式", "heading": "自定义样式",
"option_label": "自定义样式已启用" "option_label": "自定义样式已启用"
}, },
"handy_connection_key": "快速连接密钥", "handy_connection_key": {
"handy_connection_key_desc": "用于互动场景的快速连接密钥", "description": "用于互动场景的快速连接密钥",
"heading": "快速连接密钥"
},
"language": { "language": {
"heading": "语言" "heading": "语言"
}, },

View File

@@ -310,8 +310,10 @@
"heading": "自定義 CSS", "heading": "自定義 CSS",
"option_label": "啟用自定義 CSS" "option_label": "啟用自定義 CSS"
}, },
"handy_connection_key": "Handy 連線金鑰", "handy_connection_key": {
"handy_connection_key_desc": "播放支援互動性的短片時所用的 Handy 連線金鑰。", "description": "播放支援互動性的短片時所用的 Handy 連線金鑰。",
"heading": "Handy 連線金鑰"
},
"language": { "language": {
"heading": "語言" "heading": "語言"
}, },

View File

@@ -26,11 +26,13 @@ function convertFunscriptToCSV(funscript: IFunscript) {
export class Interactive { export class Interactive {
private _connected: boolean; private _connected: boolean;
private _playing: boolean; private _playing: boolean;
private _scriptOffset: number;
private _handy: Handy; private _handy: Handy;
constructor(handyKey: string) { constructor(handyKey: string, scriptOffset: number) {
this._handy = new Handy(); this._handy = new Handy();
this._handy.connectionKey = handyKey; this._handy.connectionKey = handyKey;
this._scriptOffset = scriptOffset;
this._connected = false; this._connected = false;
this._playing = false; this._playing = false;
} }
@@ -76,7 +78,7 @@ export class Interactive {
return; return;
} }
this._playing = await this._handy this._playing = await this._handy
.syncPlay(true, Math.round(position * 1000)) .syncPlay(true, Math.round(position * 1000 + this._scriptOffset))
.then(() => true); .then(() => true);
} }