diff --git a/src/components/attachment/attachment.js b/src/components/attachment/attachment.js index 775207c06047033c14e2e93a5bd947f0b01328f2..cc19714d5406e0a5adbba1813e0cf3e54ef39277 100644 --- a/src/components/attachment/attachment.js +++ b/src/components/attachment/attachment.js @@ -13,6 +13,7 @@ const Attachment = { return { nsfwImage, hideNsfwLocal: this.$store.state.config.hideNsfw, + loopVideo: this.$store.state.config.loopVideo, showHidden: false, loading: false, img: this.type === 'image' && document.createElement('img') @@ -59,6 +60,23 @@ const Attachment = { } else { this.showHidden = !this.showHidden } + }, + onVideoDataLoad (e) { + if (typeof e.srcElement.webkitAudioDecodedByteCount !== 'undefined') { + // non-zero if video has audio track + if (e.srcElement.webkitAudioDecodedByteCount > 0) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + } + } else if (typeof e.srcElement.mozHasAudio !== 'undefined') { + // true if video has audio track + if (e.srcElement.mozHasAudio) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + } + } else if (typeof e.srcElement.audioTracks !== 'undefined') { + if (e.srcElement.audioTracks.length > 0) { + this.loopVideo = this.loopVideo && !this.$store.state.config.loopVideoSilentOnly + } + } } } } diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue index 66110de435f8fcab9b256674009fe29312232252..8342f34b06b4a72bd9c59cb336e795c4a69b9356 100644 --- a/src/components/attachment/attachment.vue +++ b/src/components/attachment/attachment.vue @@ -14,7 +14,7 @@ <StillImage :class="{'small': isSmall}" referrerpolicy="no-referrer" :mimetype="attachment.mimetype" :src="attachment.large_thumb_url || attachment.url"/> </a> - <video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" :src="attachment.url" controls loop></video> + <video :class="{'small': isSmall}" v-if="type === 'video' && !hidden" @loadeddata="onVideoDataLoad" :src="attachment.url" controls :loop="loopVideo"></video> <audio v-if="type === 'audio'" :src="attachment.url" controls></audio> diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js index e5f4b001ab7cec81b492a4feeecd0a89f0cde1f7..169b90804e3f0fcf6e3a73d0fa7018823f4da232 100644 --- a/src/components/settings/settings.js +++ b/src/components/settings/settings.js @@ -1,3 +1,4 @@ +/* eslint-env browser */ import StyleSwitcher from '../style_switcher/style_switcher.vue' import { filter, trim } from 'lodash' @@ -7,12 +8,21 @@ const settings = { hideAttachmentsLocal: this.$store.state.config.hideAttachments, hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv, hideNsfwLocal: this.$store.state.config.hideNsfw, + loopVideoLocal: this.$store.state.config.loopVideo, + loopVideoSilentOnlyLocal: this.$store.state.config.loopVideoSilentOnly, muteWordsString: this.$store.state.config.muteWords.join('\n'), autoLoadLocal: this.$store.state.config.autoLoad, streamingLocal: this.$store.state.config.streaming, - pauseOnUnfocused: this.$store.state.config.pauseOnUnfocused, + pauseOnUnfocusedLocal: this.$store.state.config.pauseOnUnfocused, hoverPreviewLocal: this.$store.state.config.hoverPreview, - stopGifs: this.$store.state.config.stopGifs + stopGifs: this.$store.state.config.stopGifs, + loopSilentAvailable: + // Firefox + Object.getOwnPropertyDescriptor(HTMLVideoElement.prototype, 'mozHasAudio') || + // Chrome-likes + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'webkitAudioDecodedByteCount') || + // Future spec, still not supported in Nightly 63 as of 08/2018 + Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'audioTracks') } }, components: { @@ -33,6 +43,12 @@ const settings = { hideNsfwLocal (value) { this.$store.dispatch('setOption', { name: 'hideNsfw', value }) }, + loopVideoLocal (value) { + this.$store.dispatch('setOption', { name: 'loopVideo', value }) + }, + loopVideoSilentOnlyLocal (value) { + this.$store.dispatch('setOption', { name: 'loopVideoSilentOnly', value }) + }, autoLoadLocal (value) { this.$store.dispatch('setOption', { name: 'autoLoad', value }) }, diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue index d6dbab271bd7f9065094bd61a1358d03247f4ead..d6aec2ffbcdf80406742dc6bdb8818d87149dc61 100644 --- a/src/components/settings/settings.vue +++ b/src/components/settings/settings.vue @@ -22,7 +22,7 @@ <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]"> <li> <input :disabled="!streamingLocal" type="checkbox" id="pauseOnUnfocused" v-model="pauseOnUnfocusedLocal"> - <label for="pauseOnUnfocused">{{$t('settings.pauseOnUnfocused')}}</label> + <label for="pauseOnUnfocused">{{$t('settings.pause_on_unfocused')}}</label> </li> </ul> </li> @@ -55,6 +55,19 @@ <input type="checkbox" id="stopGifs" v-model="stopGifs"> <label for="stopGifs">{{$t('settings.stop_gifs')}}</label> </li> + <li> + <input type="checkbox" id="loopVideo" v-model="loopVideoLocal"> + <label for="loopVideo">{{$t('settings.loop_video')}}</label> + <ul class="setting-list suboptions" :class="[{disabled: !streamingLocal}]"> + <li> + <input :disabled="!loopVideoLocal || !loopSilentAvailable" type="checkbox" id="loopVideoSilentOnly" v-model="loopVideoSilentOnlyLocal"> + <label for="loopVideoSilentOnly">{{$t('settings.loop_video_silent_only')}}</label> + <div v-if="!loopSilentAvailable" class="unavailable"> + <i class="icon-globe"/>! {{$t('settings.limited_availability')}} + </div> + </li> + </ul> + </li> </ul> </div> </div> @@ -78,6 +91,12 @@ height: 100px; } + .unavailable, + .unavailable i { + color: var(--cRed, $fallback--cRed); + color: $fallback--cRed; + } + .old-avatar { width: 128px; border-radius: $fallback--avatarRadius; diff --git a/src/i18n/messages.js b/src/i18n/messages.js index d672fc41a3d16e2530a80179060ade9fb004b528..3c11af8ceab52dc8a20934ed04e28a9088a4fee4 100644 --- a/src/i18n/messages.js +++ b/src/i18n/messages.js @@ -314,7 +314,9 @@ const en = { stop_gifs: 'Play-on-hover GIFs', autoload: 'Enable automatic loading when scrolled to the bottom', streaming: 'Enable automatic streaming of new posts when scrolled to the top', - pauseOnUnfocused: 'Pause streaming when tab is not focused', + pause_on_unfocused: 'Pause streaming when tab is not focused', + loop_video: 'Loop videos', + loop_video_silent_only: 'Loop only videos without sound (i.e. Mastodon\'s "gifs")', reply_link_preview: 'Enable reply-link preview on mouse hover', follow_import: 'Follow import', import_followers_from_a_csv_file: 'Import follows from a csv file', @@ -333,7 +335,8 @@ const en = { confirm_new_password: 'Confirm new password', changed_password: 'Password changed successfully!', change_password_error: 'There was an issue changing your password.', - lock_account_description: 'Restrict your account to approved followers only' + lock_account_description: 'Restrict your account to approved followers only', + limited_availability: 'Unavailable in your browser' }, notifications: { notifications: 'Notifications', @@ -1606,6 +1609,9 @@ const ru = { nsfw_clickthrough: 'Включить Ñкрытие NSFW вложений', autoload: 'Включить автоматичеÑкую загрузку при прокрутке вниз', streaming: 'Включить автоматичеÑкую загрузку новых Ñообщений при прокрутке вверх', + pause_on_unfocused: 'ПриоÑтановить загрузку когда вкладка не в фокуÑе', + loop_video: 'Зациливать видео', + loop_video_silent_only: 'Зацикливать только беззвучные видео (Ñ‚.е. "гифки" Ñ Mastodon)', reply_link_preview: 'Включить предварительный проÑмотр ответа при наведении мыши', follow_import: 'Импортировать читаемых', import_followers_from_a_csv_file: 'Импортировать читаемых из файла .csv', @@ -1623,7 +1629,8 @@ const ru = { new_password: 'Ðовый пароль', confirm_new_password: 'Подтверждение нового паролÑ', changed_password: 'Пароль изменён уÑпешно.', - change_password_error: 'Произошла ошибка при попытке изменить пароль.' + change_password_error: 'Произошла ошибка при попытке изменить пароль.', + limited_availability: 'Ðе доÑтупно в вашем браузере' }, notifications: { notifications: 'УведомлениÑ', diff --git a/src/modules/config.js b/src/modules/config.js index 2b50655be0a3d8ddef785387e8010b2f3a142c2d..26930e1d36ca2dd9139accd25e0efd74d5d8a8ea 100644 --- a/src/modules/config.js +++ b/src/modules/config.js @@ -7,9 +7,12 @@ const defaultState = { hideAttachments: false, hideAttachmentsInConv: false, hideNsfw: true, + loopVideo: true, + loopVideoSilentOnly: true, autoLoad: true, streaming: false, hoverPreview: true, + pauseOnUnfocused: true, muteWords: [], highlight: {} }