mirror of
https://github.com/stashapp/stash.git
synced 2026-02-07 16:05:47 +01:00
Merge 44f51f45df into 8dec195c2d
This commit is contained in:
commit
b21214e1a5
7 changed files with 70 additions and 4 deletions
|
|
@ -416,6 +416,9 @@ input ConfigInterfaceInput {
|
|||
noBrowser: Boolean
|
||||
"True if we should send notifications to the desktop"
|
||||
notificationsEnabled: Boolean
|
||||
|
||||
"True if 'Open in Ext. Player' button in scene details is shown"
|
||||
showOpenExternal: Boolean
|
||||
}
|
||||
|
||||
type ConfigDisableDropdownCreate {
|
||||
|
|
@ -489,6 +492,9 @@ type ConfigInterfaceResult {
|
|||
funscriptOffset: Int
|
||||
"Whether to use Stash Hosted Funscript"
|
||||
useStashHostedFunscript: Boolean
|
||||
|
||||
"Show 'Open in Ext. Player' button in scene details"
|
||||
showOpenExternal: Boolean
|
||||
}
|
||||
|
||||
input ConfigDLNAInput {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -68,6 +70,7 @@ func (rs sceneRoutes) Routes() chi.Router {
|
|||
r.Get("/stream.mpd", rs.StreamDASH)
|
||||
r.Get("/stream.mpd/{segment}_v.webm", rs.StreamDASHVideoSegment)
|
||||
r.Get("/stream.mpd/{segment}_a.webm", rs.StreamDASHAudioSegment)
|
||||
r.Get("/stream/org/{streamOrgFile}", rs.StreamOrgDirect)
|
||||
|
||||
r.Get("/screenshot", rs.Screenshot)
|
||||
r.Get("/preview", rs.Preview)
|
||||
|
|
@ -99,6 +102,44 @@ func (rs sceneRoutes) StreamDirect(w http.ResponseWriter, r *http.Request) {
|
|||
ss.StreamSceneDirect(scene, w, r)
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) StreamOrgDirect(w http.ResponseWriter, r *http.Request) {
|
||||
scene := r.Context().Value(sceneKey).(*models.Scene)
|
||||
// check if it's funscript
|
||||
streamOrgFile := chi.URLParam(r, "streamOrgFile")
|
||||
aStr := strings.Split(streamOrgFile, ".")
|
||||
// aStr usually is the primary file, but can be .srt or other file format.
|
||||
if strings.ToLower(aStr[len(aStr)-1]) == "funscript" {
|
||||
// it's a funscript request
|
||||
rs.Funscript(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// return 404 if the scene does not have a primary file
|
||||
primaryFile := scene.Files.Primary()
|
||||
if primaryFile == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
if _, err := w.Write([]byte("Primary file not found for streaming original file.")); err != nil {
|
||||
logger.Warnf("[scene] error getting primary file for streaming original: $v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
pathBase := primaryFile.Path[:len(primaryFile.Path)-len(primaryFile.Basename)] // remove filename from the path.
|
||||
f, err := url.PathUnescape(streamOrgFile)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
f = pathBase + f
|
||||
// Also return 404 if the actual file cannot be found
|
||||
if _, err := os.Stat(f); errors.Is(err, os.ErrNotExist) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
utils.ServeStaticFile(w, r, f)
|
||||
// http.ServeFile(w, r, f.Path)
|
||||
}
|
||||
|
||||
func (rs sceneRoutes) StreamMp4(w http.ResponseWriter, r *http.Request) {
|
||||
rs.streamTranscode(w, r, ffmpeg.StreamTypeMP4)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
|
|||
handyKey
|
||||
funscriptOffset
|
||||
useStashHostedFunscript
|
||||
showOpenExternal
|
||||
}
|
||||
|
||||
fragment ConfigDLNAData on ConfigDLNAResult {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { useIntl } from "react-intl";
|
|||
import { Icon } from "src/components/Shared/Icon";
|
||||
import { objectTitle } from "src/core/files";
|
||||
import { SceneDataFragment } from "src/core/generated-graphql";
|
||||
import { useConfigurationContext } from "src/hooks/Config";
|
||||
|
||||
export interface IExternalPlayerButtonProps {
|
||||
scene: SceneDataFragment;
|
||||
|
|
@ -16,10 +17,17 @@ export const ExternalPlayerButton: React.FC<IExternalPlayerButtonProps> = ({
|
|||
const isAndroid = /(android)/i.test(navigator.userAgent);
|
||||
const isAppleDevice = /(ipod|iphone|ipad)/i.test(navigator.userAgent);
|
||||
const intl = useIntl();
|
||||
const { configuration } = useConfigurationContext();
|
||||
const showOpenExternal = configuration.ui.showOpenExternal ?? true;
|
||||
const { paths, files } = scene;
|
||||
// Get only file name from the full path.
|
||||
const fileName = files[0].path?.split("/").pop()?.split("\\").pop() ?? "";
|
||||
|
||||
const { paths } = scene;
|
||||
|
||||
if (!paths || !paths.stream || (!isAndroid && !isAppleDevice))
|
||||
if (
|
||||
!paths ||
|
||||
!paths.stream ||
|
||||
(!isAndroid && !isAppleDevice && !showOpenExternal)
|
||||
)
|
||||
return <span />;
|
||||
|
||||
const { stream } = paths;
|
||||
|
|
@ -47,6 +55,8 @@ export const ExternalPlayerButton: React.FC<IExternalPlayerButtonProps> = ({
|
|||
url = streamURL
|
||||
.toString()
|
||||
.replace(new RegExp(`^${streamURL.protocol}`), "vlc-x-callback:");
|
||||
} else if (showOpenExternal) {
|
||||
url = stream + "/org/" + encodeURIComponent(fileName);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -462,13 +462,18 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
|
|||
return <span>{TextUtils.secondsToTimestamp(v ?? 0)}</span>;
|
||||
}}
|
||||
/>
|
||||
|
||||
<BooleanSetting
|
||||
id="show-ab-loop"
|
||||
headingID="config.ui.scene_player.options.show_ab_loop_controls"
|
||||
checked={ui.showAbLoopControls ?? undefined}
|
||||
onChange={(v) => saveUI({ showAbLoopControls: v })}
|
||||
/>
|
||||
<BooleanSetting
|
||||
id="show-open-external"
|
||||
headingID="config.ui.scene_player.options.show_open_external"
|
||||
checked={ui.showOpenExternal ?? true}
|
||||
onChange={(v) => saveUI({ showOpenExternal: v })}
|
||||
/>
|
||||
</SettingSection>
|
||||
<SettingSection headingID="config.ui.tag_panel.heading">
|
||||
<BooleanSetting
|
||||
|
|
|
|||
|
|
@ -83,6 +83,8 @@ export interface IUIConfig {
|
|||
|
||||
showAbLoopControls?: boolean;
|
||||
|
||||
showOpenExternal?: boolean;
|
||||
|
||||
// maximum number of items to shown in the dropdown list - defaults to 200
|
||||
// upper limit of 1000
|
||||
maxOptionsShown?: number;
|
||||
|
|
|
|||
|
|
@ -798,6 +798,7 @@
|
|||
"disable_mobile_media_auto_rotate": "Disable auto-rotate of fullscreen media on Mobile",
|
||||
"enable_chromecast": "Enable Chromecast",
|
||||
"show_ab_loop_controls": "Show AB Loop plugin controls",
|
||||
"show_open_external": "Show 'Open In External Player' button",
|
||||
"show_scrubber": "Show Scrubber",
|
||||
"show_range_markers": "Show Range Markers",
|
||||
"track_activity": "Enable Scene Play history",
|
||||
|
|
|
|||
Loading…
Reference in a new issue