diff --git a/frontend/src/components/player/AudioPlayer.vue b/frontend/src/components/player/AudioPlayer.vue index 55b47293..5a65fe14 100644 --- a/frontend/src/components/player/AudioPlayer.vue +++ b/frontend/src/components/player/AudioPlayer.vue @@ -21,7 +21,7 @@ playsinline webkit-playsinline x-webkit-airplay="allow" - :loop="loop && !sliceLoop" + :loop="(loop || isShort) && !sliceLoop" crossorigin="use-credentials" :muted="muted" :volume.prop="volume" @@ -300,6 +300,8 @@ @update:nextEnd="onUpdateNextEnd" @enter="enterControls" @leave="leaveControls" + :isShort="isShort" + @update-auto-next="setupAutoNextTimer" > @@ -360,6 +363,7 @@ import { AppStatus } from "@/control/app-status"; import { KeyboardManager } from "@/control/keyboard"; import { AuthController } from "@/control/auth"; import { AppPreferences } from "@/control/app-preferences"; +import { AUTO_LOOP_MIN_DURATION } from "@/utils/constants"; export default defineComponent({ components: { @@ -447,6 +451,8 @@ export default defineComponent({ loop: false, nextEnd: false, + isShort: false, + sliceLoop: false, volume: 1, @@ -653,7 +659,7 @@ export default defineComponent({ if (this.canSaveTime) { PlayerPreferences.SetInitialTime(this.mid, 0); } - if (!this.loop) { + if (!this.loop && !this.isShort) { this.pause(); this.ended = true; if (this.nextEnd) { @@ -1072,7 +1078,7 @@ export default defineComponent({ break; case "l": case "L": - if (event.altKey || shifting) { + if (event.altKey || shifting || this.isShort) { caught = false; } else { this.loop = !this.loop; @@ -1114,6 +1120,7 @@ export default defineComponent({ if (!this.metadata) { return; } + this.isShort = this.metadata.duration <= AUTO_LOOP_MIN_DURATION; this.canSaveTime = !this.metadata.force_start_beginning; this.hasExtendedDescription = !!this.metadata.ext_desc_url; this.timeSlices = normalizeTimeSlices( @@ -1167,6 +1174,7 @@ export default defineComponent({ this.audioPending = false; this.audioPendingTask = 0; this.getAudioElement().load(); + this.setupAutoNextTimer(); } else { this.audioURL = ""; this.onClearURL(); @@ -1427,7 +1435,7 @@ export default defineComponent({ if (this.loopForced) { this.loop = this.loopForcedValue; } else { - this.loop = AppStatus.CurrentAlbum < 0 || !this.nextEnd; + this.loop = (!this.next && !this.pageNext) || !this.nextEnd; } }, @@ -1436,6 +1444,33 @@ export default defineComponent({ this.pause(); } }, + + setupAutoNextTimer: function () { + if (this._handles.autoNextTimer) { + clearTimeout(this._handles.autoNextTimer); + this._handles.autoNextTimer = null; + } + const timerS = PlayerPreferences.ImageAutoNext; + + if (isNaN(timerS) || !isFinite(timerS) || timerS <= 0) { + return; + } + + if (!this.next && !this.pageNext) { + return; + } + + const ms = timerS * 1000; + + this._handles.autoNextTimer = setTimeout(() => { + this._handles.autoNextTimer = null; + if (this.displayConfig || this.expandedTitle) { + this.setupAutoNextTimer(); + } else { + this.goNext(); + } + }, ms); + }, }, mounted: function () { this._handles = Object.create(null); @@ -1479,6 +1514,11 @@ export default defineComponent({ this.onClearURL(); clearInterval(this._handles.timer); + if (this._handles.autoNextTimer) { + clearTimeout(this._handles.autoNextTimer); + this._handles.autoNextTimer = null; + } + this.clearAudioRenderer(); this.closeAudioContext(); @@ -1515,8 +1555,13 @@ export default defineComponent({ this.loading = true; } }, - inAlbum: function () { + next: function () { + this.setDefaultLoop(); + this.setupAutoNextTimer(); + }, + pageNext: function () { this.setDefaultLoop(); + this.setupAutoNextTimer(); }, }, }); diff --git a/frontend/src/components/player/AudioPlayerConfig.vue b/frontend/src/components/player/AudioPlayerConfig.vue index 9261fc8c..09bb6e81 100644 --- a/frontend/src/components/player/AudioPlayerConfig.vue +++ b/frontend/src/components/player/AudioPlayerConfig.vue @@ -14,7 +14,7 @@ @keydown="keyDownHandle" > - + - + + + + +
{{ $t("Loop") }} @@ -23,7 +23,7 @@
{{ $t("Auto next") }} @@ -32,6 +32,16 @@
+ + {{ $t("Auto next") }} + + {{ renderAutoNext(autoNext) }} + +
@@ -217,6 +227,23 @@
+ + + + + + + + + + +
+ + {{ $t("Auto next") }} +
+ + {{ renderAutoNext(b) }} +
@@ -254,6 +281,7 @@ export default defineComponent({ subBackground: String, subHTML: Boolean, rTick: Number, + isShort: Boolean, }, setup(props) { return { @@ -277,6 +305,9 @@ export default defineComponent({ subtitlesSizes: ["s", "m", "l", "xl", "xxl"], subtitlesBackgrounds: ["100", "75", "50", "25", "0"], + + autoNext: PlayerPreferences.ImageAutoNext, + autoNextOptions: [0, 3, 5, 10, 15, 20, 25, 30], }; }, methods: { @@ -332,6 +363,17 @@ export default defineComponent({ this.focus(); }, + goToAutoNext: function () { + this.page = "auto-next"; + this.focus(); + }, + + changeAutoNext: function (b) { + this.autoNext = b; + PlayerPreferences.SetImageAutoNext(b); + this.$emit("update-auto-next"); + }, + renderSpeed: function (speed: number) { if (speed > 1) { return Math.floor(speed * 100) + "%"; @@ -398,6 +440,18 @@ export default defineComponent({ } }, + renderAutoNext: function (s: number) { + if (!isNaN(s) && isFinite(s) && s > 0) { + if (s === 1) { + return s + " " + this.$t("second"); + } else { + return s + " " + this.$t("seconds"); + } + } else { + return this.$t("Disabled"); + } + }, + updateSubtitleBackground: function (s: string) { this.subBackgroundState = s; PlayerPreferences.SetSubtitlesBackground(s); diff --git a/frontend/src/components/player/PlayerContextMenu.vue b/frontend/src/components/player/PlayerContextMenu.vue index 395cd2be..7272da44 100644 --- a/frontend/src/components/player/PlayerContextMenu.vue +++ b/frontend/src/components/player/PlayerContextMenu.vue @@ -20,7 +20,7 @@ @dblclick="stopPropagationEvent" > - +
{{ $t("Loop") }} @@ -159,6 +159,8 @@ export default defineComponent({ sliceLoop: Boolean, hasSlices: Boolean, + isShort: Boolean, + notesEdit: Boolean, canWrite: Boolean, diff --git a/frontend/src/components/player/VideoPlayer.vue b/frontend/src/components/player/VideoPlayer.vue index a0ac71a7..f67efc64 100644 --- a/frontend/src/components/player/VideoPlayer.vue +++ b/frontend/src/components/player/VideoPlayer.vue @@ -25,7 +25,7 @@ webkit-playsinline x-webkit-airplay="allow" :muted="muted || !!audioTrackURL" - :loop="loop && !sliceLoop" + :loop="(loop || isShort) && !sliceLoop" :volume.prop="volume" :playbackRate.prop="speed" @ended="onEnded" @@ -321,6 +321,8 @@ @leave="leaveControls" v-model:audioTrack="audioTrack" @update:audioTrack="onUpdateAudioTrack" + :isShort="isShort" + @update-auto-next="setupAutoNextTimer" > { + this._handles.autoNextTimer = null; + if (this.displayConfig || this.expandedTitle) { + this.setupAutoNextTimer(); + } else { + this.goNext(); + } + }, ms); + }, + }, mounted: function () { this._handles = Object.create(null); @@ -1563,6 +1605,11 @@ export default defineComponent({ this._handles.togglePlayDelayTimeout = null; } + if (this._handles.autoNextTimer) { + clearTimeout(this._handles.autoNextTimer); + this._handles.autoNextTimer = null; + } + document.removeEventListener("fullscreenchange", this._handles.exitFullScreenListener); document.removeEventListener("webkitfullscreenchange", this._handles.exitFullScreenListener); document.removeEventListener("mozfullscreenchange", this._handles.exitFullScreenListener); @@ -1593,8 +1640,13 @@ export default defineComponent({ this.loading = true; } }, - inAlbum: function () { + next: function () { + this.setDefaultLoop(); + this.setupAutoNextTimer(); + }, + pageNext: function () { this.setDefaultLoop(); + this.setupAutoNextTimer(); }, }, }); diff --git a/frontend/src/components/player/VideoPlayerConfig.vue b/frontend/src/components/player/VideoPlayerConfig.vue index 78a99a29..de14faff 100644 --- a/frontend/src/components/player/VideoPlayerConfig.vue +++ b/frontend/src/components/player/VideoPlayerConfig.vue @@ -14,7 +14,7 @@ @keydown="keyDownHandle" > - + - + + + + +
{{ $t("Loop") }} @@ -23,7 +23,7 @@
{{ $t("Auto next") }} @@ -32,6 +32,16 @@
+ + {{ $t("Auto next") }} + + {{ renderAutoNext(autoNext) }} + +
@@ -312,6 +322,23 @@
+ + + + + + + + + + +
+ + {{ $t("Auto next") }} +
+ + {{ renderAutoNext(b) }} +
@@ -351,6 +378,7 @@ export default defineComponent({ subHTML: Boolean, rTick: Number, audioTrack: String, + isShort: Boolean, }, setup(props) { return { @@ -377,6 +405,9 @@ export default defineComponent({ toggleDelay: PlayerPreferences.PlayerTogglePlayDelay, toggleDelayOptions: [0, 250, 500, 750, 1000], + + autoNext: PlayerPreferences.ImageAutoNext, + autoNextOptions: [0, 3, 5, 10, 15, 20, 25, 30], }; }, methods: { @@ -400,6 +431,12 @@ export default defineComponent({ PlayerPreferences.SetAudioTrack(s); }, + changeAutoNext: function (b) { + this.autoNext = b; + PlayerPreferences.SetImageAutoNext(b); + this.$emit("update-auto-next"); + }, + focus: function () { nextTick(() => { this.$el.focus(); @@ -462,6 +499,11 @@ export default defineComponent({ this.focus(); }, + goToAutoNext: function () { + this.page = "auto-next"; + this.focus(); + }, + renderSpeed: function (speed: number) { if (speed > 1) { return Math.floor(speed * 100) + "%"; @@ -565,6 +607,18 @@ export default defineComponent({ } }, + renderAutoNext: function (s: number) { + if (!isNaN(s) && isFinite(s) && s > 0) { + if (s === 1) { + return s + " " + this.$t("second"); + } else { + return s + " " + this.$t("seconds"); + } + } else { + return this.$t("Disabled"); + } + }, + updateSubtitleSize: function (s: string) { this.subSizeState = s; PlayerPreferences.SetSubtitlesSize(s); diff --git a/frontend/src/utils/constants.ts b/frontend/src/utils/constants.ts index 6431abd5..9425a4ba 100644 --- a/frontend/src/utils/constants.ts +++ b/frontend/src/utils/constants.ts @@ -4,3 +4,8 @@ export const MEDIA_TYPE_DELETED = 0; export const MEDIA_TYPE_IMAGE = 1; export const MEDIA_TYPE_VIDEO = 2; export const MEDIA_TYPE_AUDIO = 3; + +/** + * Min duration in seconds to use auto-next, instead of next-end + */ +export const AUTO_LOOP_MIN_DURATION = 3;