Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions graphql/schema/types/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ input ConfigInterfaceInput {
autostartVideoOnPlaySelected: Boolean
"If true, next scene in playlist will be played at video end by default"
continuePlaylistDefault: Boolean
"If true, queue wraps to the first scene after the last scene finishes by default"
loopPlaylistDefault: Boolean

"If true, studio overlays will be shown as text instead of logo images"
showStudioAsText: Boolean
Expand Down Expand Up @@ -478,6 +480,8 @@ type ConfigInterfaceResult {
autostartVideoOnPlaySelected: Boolean
"If true, next scene in playlist will be played at video end by default"
continuePlaylistDefault: Boolean
"If true, queue wraps to the first scene after the last scene finishes by default"
loopPlaylistDefault: Boolean

"If true, studio overlays will be shown as text instead of logo images"
showStudioAsText: Boolean
Expand Down
1 change: 1 addition & 0 deletions internal/api/resolver_mutation_configure.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
r.setConfigBool(config.ShowStudioAsText, input.ShowStudioAsText)
r.setConfigBool(config.AutostartVideoOnPlaySelected, input.AutostartVideoOnPlaySelected)
r.setConfigBool(config.ContinuePlaylistDefault, input.ContinuePlaylistDefault)
r.setConfigBool(config.LoopPlaylistDefault, input.LoopPlaylistDefault)

r.setConfigString(config.Language, input.Language)

Expand Down
2 changes: 2 additions & 0 deletions internal/api/resolver_query_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
autostartVideo := config.GetAutostartVideo()
autostartVideoOnPlaySelected := config.GetAutostartVideoOnPlaySelected()
continuePlaylistDefault := config.GetContinuePlaylistDefault()
loopPlaylistDefault := config.GetLoopPlaylistDefault()
showStudioAsText := config.GetShowStudioAsText()
css := config.GetCSS()
cssEnabled := config.GetCSSEnabled()
Expand Down Expand Up @@ -183,6 +184,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
ShowStudioAsText: &showStudioAsText,
AutostartVideoOnPlaySelected: &autostartVideoOnPlaySelected,
ContinuePlaylistDefault: &continuePlaylistDefault,
LoopPlaylistDefault: &loopPlaylistDefault,
CSS: &css,
CSSEnabled: &cssEnabled,
Javascript: &javascript,
Expand Down
5 changes: 5 additions & 0 deletions internal/manager/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ const (
AutostartVideoOnPlaySelected = "autostart_video_on_play_selected"
autostartVideoOnPlaySelectedDefault = true
ContinuePlaylistDefault = "continue_playlist_default"
LoopPlaylistDefault = "loop_playlist_default"
ShowStudioAsText = "show_studio_as_text"
CSSEnabled = "cssenabled"
JavascriptEnabled = "javascriptenabled"
Expand Down Expand Up @@ -1326,6 +1327,10 @@ func (i *Config) GetContinuePlaylistDefault() bool {
return i.getBool(ContinuePlaylistDefault)
}

func (i *Config) GetLoopPlaylistDefault() bool {
return i.getBool(LoopPlaylistDefault)
}

func (i *Config) GetShowStudioAsText() bool {
return i.getBool(ShowStudioAsText)
}
Expand Down
1 change: 1 addition & 0 deletions internal/manager/config/config_concurrency_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func TestConcurrentConfigAccess(t *testing.T) {
i.SetInterface(DisableDropdownCreateMovie, i.GetDisableDropdownCreate().Movie)
i.SetInterface(AutostartVideoOnPlaySelected, i.GetAutostartVideoOnPlaySelected())
i.SetInterface(ContinuePlaylistDefault, i.GetContinuePlaylistDefault())
i.SetInterface(LoopPlaylistDefault, i.GetLoopPlaylistDefault())
i.SetInterface(PythonPath, i.GetPythonPath())
t.Logf("Worker %v iteration %v took %v", wk, l, time.Since(start))
}
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/graphql/data/config.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
autostartVideo
autostartVideoOnPlaySelected
continuePlaylistDefault
loopPlaylistDefault
showStudioAsText
css
cssEnabled
Expand Down
12 changes: 12 additions & 0 deletions ui/v2.5/src/components/Scenes/SceneDetails/QueueViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ export interface IPlaylistViewer {
currentID?: string;
start?: number;
continue?: boolean;
loop?: boolean;
hasMoreScenes: boolean;
setContinue: (v: boolean) => void;
setLoop: (v: boolean) => void;
onSceneClicked: (id: string) => void;
onNext: () => void;
onPrevious: () => void;
Expand All @@ -34,8 +36,10 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
currentID,
start = 0,
continue: continuePlaylist = false,
loop: loopPlaylist = false,
hasMoreScenes,
setContinue,
setLoop,
onNext,
onPrevious,
onRandom,
Expand Down Expand Up @@ -127,6 +131,14 @@ export const QueueViewer: React.FC<IPlaylistViewer> = ({
setContinue(!continuePlaylist);
}}
/>
<Form.Check
id="loop-checkbox"
checked={loopPlaylist}
label={intl.formatMessage({ id: "actions.loop" })}
onChange={() => {
setLoop(!loopPlaylist);
}}
/>
</div>
<div>
{currentIndex > 0 || start > 1 ? (
Expand Down
29 changes: 24 additions & 5 deletions ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ interface IProps {
collapsed: boolean;
setCollapsed: (state: boolean) => void;
setContinuePlaylist: (value: boolean) => void;
loopPlaylist: boolean;
setLoopPlaylist: (value: boolean) => void;
}

interface ISceneParams {
Expand Down Expand Up @@ -183,6 +185,8 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
collapsed,
setCollapsed,
setContinuePlaylist,
loopPlaylist,
setLoopPlaylist,
} = props;

const Toast = useToast();
Expand Down Expand Up @@ -598,6 +602,8 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
currentID={scene.id}
continue={continuePlaylist}
setContinue={setContinuePlaylist}
loop={loopPlaylist}
setLoop={setLoopPlaylist}
onSceneClicked={onQueueSceneClicked}
onNext={onQueueNext}
onPrevious={onQueuePrevious}
Expand Down Expand Up @@ -672,9 +678,8 @@ const ScenePage: React.FC<IProps> = PatchComponent("ScenePage", (props) => {
{maybeRenderMergeDialog()}
{maybeRenderDeleteDialog()}
<div
className={`scene-tabs order-xl-first order-last ${
collapsed ? "collapsed" : ""
}`}
className={`scene-tabs order-xl-first order-last ${collapsed ? "collapsed" : ""
}`}
>
<div>
<div className="scene-header-container">
Expand Down Expand Up @@ -785,10 +790,19 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
}
}, [configuration?.interface.continuePlaylistDefault, queryParams]);

const queryLoop = useMemo(() => {
const loop = queryParams.get("loop");
if (loop) {
return loop === "true";
}
return !!configuration?.interface.loopPlaylistDefault;
}, [configuration?.interface.loopPlaylistDefault, queryParams]);

const [queueScenes, setQueueScenes] = useState<QueuedScene[]>([]);

const [collapsed, setCollapsed] = useState(false);
const [continuePlaylist, setContinuePlaylist] = useState(queryContinue);
const [loopPlaylist, setLoopPlaylist] = useState(queryLoop);
const [hideScrubber, setHideScrubber] = useState(
!(configuration?.interface.showScrubber ?? true)
);
Expand Down Expand Up @@ -904,11 +918,12 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
newPage,
autoPlay,
continue: continuePlaylist,
loop: loopPlaylist,
});
history.replace(sceneLink);
}

async function queueNext(autoPlay: boolean) {
async function queueNext(autoPlay: boolean, allowLoop = false) {
if (currentQueueIndex === -1) return;

if (currentQueueIndex < queueScenes.length - 1) {
Expand All @@ -922,6 +937,8 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
const newPage = (sceneQueue.query?.currentPage ?? 0) + 1;
loadScene(loadedScenes[0].id, autoPlay, newPage);
}
} else if (allowLoop && loopPlaylist && queueScenes.length > 0) {
loadScene(queueScenes[0].id, autoPlay);
}
}
}
Expand Down Expand Up @@ -972,7 +989,7 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
function onComplete() {
// load the next scene if we're continuing
if (continuePlaylist) {
queueNext(true);
queueNext(true, true);
}
}

Expand Down Expand Up @@ -1029,6 +1046,8 @@ const SceneLoader: React.FC<RouteComponentProps<ISceneParams>> = ({
collapsed={collapsed}
setCollapsed={setCollapsed}
setContinuePlaylist={setContinuePlaylist}
loopPlaylist={loopPlaylist}
setLoopPlaylist={setLoopPlaylist}
/>
<div className={`scene-player-container ${collapsed ? "expanded" : ""}`}>
<ScenePlayer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,14 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
onChange={(v) => saveInterface({ continuePlaylistDefault: v })}
/>

<BooleanSetting
id="loop-playlist-default"
headingID="config.ui.scene_player.options.loop_playlist_default.heading"
subHeadingID="config.ui.scene_player.options.loop_playlist_default.description"
checked={iface.loopPlaylistDefault ?? undefined}
onChange={(v) => saveInterface({ loopPlaylistDefault: v })}
/>

<ModalSetting<number>
id="max-loop-duration"
headingID="config.ui.max_loop_duration.heading"
Expand Down Expand Up @@ -807,26 +815,26 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
</SelectSetting>
{(ui.ratingSystemOptions?.type ?? defaultRatingSystemType) ===
RatingSystemType.Stars && (
<SelectSetting
id="rating_system_star_precision"
headingID="config.ui.editing.rating_system.star_precision.label"
value={
ui.ratingSystemOptions?.starPrecision ??
defaultRatingStarPrecision
}
onChange={(v) =>
saveRatingSystemStarPrecision(v as RatingStarPrecision)
}
>
{Array.from(ratingStarPrecisionIntlMap.entries()).map((v) => (
<option key={v[0]} value={v[0]}>
{intl.formatMessage({
id: v[1],
})}
</option>
))}
</SelectSetting>
)}
<SelectSetting
id="rating_system_star_precision"
headingID="config.ui.editing.rating_system.star_precision.label"
value={
ui.ratingSystemOptions?.starPrecision ??
defaultRatingStarPrecision
}
onChange={(v) =>
saveRatingSystemStarPrecision(v as RatingStarPrecision)
}
>
{Array.from(ratingStarPrecisionIntlMap.entries()).map((v) => (
<option key={v[0]} value={v[0]}>
{intl.formatMessage({
id: v[1],
})}
</option>
))}
</SelectSetting>
)}
</SettingSection>

<SettingSection headingID="config.ui.custom_css.heading">
Expand Down
5 changes: 5 additions & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"hide_configuration": "Hide Configuration",
"identify": "Identify",
"ignore": "Ignore",
"loop": "Loop",
"import": "Import…",
"import_from_file": "Import from file",
"load": "Load",
Expand Down Expand Up @@ -842,6 +843,10 @@
"description": "Play next scene in queue when video finishes.",
"heading": "Continue playlist by default"
},
"loop_playlist_default": {
"description": "When the last scene in the queue finishes, continue from the first scene.",
"heading": "Loop playlist by default"
},
"disable_mobile_media_auto_rotate": "Disable auto-rotate of fullscreen media on mobile",
"enable_chromecast": "Enable Chromecast",
"show_ab_loop_controls": "Show AB loop controls",
Expand Down
4 changes: 4 additions & 0 deletions ui/v2.5/src/models/sceneQueue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface IPlaySceneOptions {
newPage?: number;
autoPlay?: boolean;
continue?: boolean;
loop?: boolean;
start?: number;
}

Expand Down Expand Up @@ -117,6 +118,9 @@ export class SceneQueue {
if (options.continue !== undefined) {
params.push("continue=" + options.continue);
}
if (options.loop !== undefined) {
params.push("loop=" + options.loop);
}
if (options.start !== undefined) {
params.push("t=" + options.start);
}
Expand Down
Loading