···318318 * * Listened duraton is more than 2 minutes/50% of Play OR...
319319 * * Previous Position was close to end of Play
320320 */
321321- protected isSessionRepeat(position?: number, reportedTS?: Dayjs) {
321321+ protected isSessionRepeat(position?: number, reportedTS?: Dayjs): boolean {
322322 if(this.currentListenRange === undefined) {
323323 return false;
324324 }
···11+import { SourcePlayerObj } from "../../../core/Atomic.js";
22+import { GenericPlayerState } from "./GenericPlayerState.js";
33+import { Dayjs } from "dayjs";
44+55+export class SubsonicPlayerState extends GenericPlayerState {
66+77+ protected isSessionRepeat(position?: number, reportedTS?: Dayjs) {
88+ if(super.isSessionRepeat()) {
99+ return true;
1010+ }
1111+ // if track has a duration and the listened duration for this session is greater than 100% + 5% (for buffer)
1212+ // then assume track is on repeat
1313+ if(this.currentPlay.data.duration !== undefined && this.getListenDuration() > (this.currentPlay.data.duration + (0.05 * this.currentPlay.data.duration))) {
1414+ this.logger.debug('Listened duration for this session is over 105%, triggering as a repeat');
1515+ return true;
1616+ }
1717+ return false;
1818+ }
1919+}
+6-1
src/backend/sources/SubsonicSource.ts
···66import { PlayObject } from "../../core/Atomic.js";
77import { isNodeNetworkException } from "../common/errors/NodeErrors.js";
88import { UpstreamError } from "../common/errors/UpstreamError.js";
99-import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig } from "../common/infrastructure/Atomic.js";
99+import { DEFAULT_RETRY_MULTIPLIER, FormatPlayObjectOptions, InternalConfig, PlayPlatformId } from "../common/infrastructure/Atomic.js";
1010import { SubSonicSourceConfig } from "../common/infrastructure/config/source/subsonic.js";
1111import { getSubsonicResponse, SubsonicResponse, SubsonicResponseCommon } from "../common/vendor/subsonic/interfaces.js";
1212import { parseRetryAfterSecsFromObj, removeDuplicates, sleep } from "../utils.js";
1313import { findCauseByFunc } from "../utils/ErrorUtils.js";
1414import { RecentlyPlayedOptions } from "./AbstractSource.js";
1515import MemorySource from "./MemorySource.js";
1616+import { SubsonicPlayerState } from './PlayerState/SubsonicPlayerState.js';
1717+import { PlayerStateOptions } from './PlayerState/AbstractPlayerState.js';
1818+import { Logger } from '@foxxmd/logging';
16191720dayjs.extend(isSameOrAfter);
1821···291294 const userFiltered = this.usersAllow.length == 0 ? deduped : deduped.filter(x => x.meta.user === undefined || this.usersAllow.map(x => x.toLocaleLowerCase()).includes(x.meta.user.toLocaleLowerCase()));
292295 return this.processRecentPlays(userFiltered);
293296 }
297297+298298+ getNewPlayer = (logger: Logger, id: PlayPlatformId, opts: PlayerStateOptions) => new SubsonicPlayerState(logger, id, opts);
294299}
295300296301export const getSubsonicResponseFromError = (error: unknown): UpstreamError => findCauseByFunc(error, (err) => {