Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
Binary file added img/LibremEmailNotification.ogg
Binary file not shown.
Binary file added img/LibremPhoneCall.ogg
Binary file not shown.
Binary file added img/LibremPowerOn.ogg
Binary file not shown.
Binary file added img/LibremTextMessage.ogg
Binary file not shown.
Binary file added img/LibremVideoCall.ogg
Binary file not shown.
3 changes: 3 additions & 0 deletions lib/Controller/SettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ protected function validateUserSetting(string $setting, $value): bool {
return (int) $value === Participant::PRIVACY_PUBLIC ||
(int) $value === Participant::PRIVACY_PRIVATE;
}
if ($setting === 'play_sounds') {
return $value === 'yes' || $value === 'no';
}

return false;
}
Expand Down
10 changes: 10 additions & 0 deletions lib/TInitialState.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ protected function publishInitialStateForUser(IUser $user, IRootFolder $rootFold
$this->talkConfig->getUserReadPrivacy($user->getUID())
);

$this->initialState->provideInitialState(
'play_sounds',
$this->serverConfig->getUserValue($user->getUID(), 'spreed', 'play_sounds', 'yes') === 'yes'
);

$attachmentFolder = $this->talkConfig->getAttachmentFolder($user->getUID());
$freeSpace = 0;

Expand Down Expand Up @@ -167,5 +172,10 @@ protected function publishInitialStateForGuest(): void {
'enable_matterbridge',
false
);

$this->initialState->provideInitialState(
'play_sounds',
false
);
}
}
32 changes: 31 additions & 1 deletion src/components/SettingsDialog/SettingsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@
@change="toggleReadStatusPrivacy">
<label for="read_status_privacy">{{ t('spreed', 'Share my read-status and show the read-status of others') }}</label>
</AppSettingsSection>
<AppSettingsSection
:title="t('spreed', 'Sounds')"
class="app-settings-section">
<input id="play_sounds"
v-model="playSounds"
:checked="readStatusPrivacyIsPublic"
:disabled="privacyLoading"
type="checkbox"
class="checkbox">
<label for="play_sounds">{{ t('settings', 'Play sounds when participants join a call or leave it') }}</label>
<em>{{ t('settings', 'Sounds can currently not be played in Safari browser and iPad and iPhone devices due to technical restrictions by the manufacturer.') }}</em>
</AppSettingsSection>
<AppSettingsSection :title="t('spreed', 'Keyboard shortcuts')">
<p>{{ t('spreed', 'Speed up your Talk experience with these quick shortcuts.') }}</p>

Expand Down Expand Up @@ -117,7 +129,10 @@

<script>
import { getFilePickerBuilder, showError, showSuccess } from '@nextcloud/dialogs'
import { setAttachmentFolder } from '../../services/settingsService'
import {
setAttachmentFolder,
setPlaySounds,
} from '../../services/settingsService'
import { PRIVACY } from '../../constants'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import MediaDevicesPreview from '../MediaDevicesPreview'
Expand All @@ -142,6 +157,21 @@ export default {
},

computed: {
// Local settings
playSounds: {
get() {
return this.$store.getters.playSounds
},
set(status) {
this.$store.commit('setPlaySounds', status)
try {
setPlaySounds(status)
} catch (e) {
showError(t('spreed', 'Failed to save sounds setting'))
}
},
},

attachmentFolder() {
return this.$store.getters.getAttachmentFolder()
},
Expand Down
15 changes: 15 additions & 0 deletions src/services/settingsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

import axios from '@nextcloud/axios'
import { generateOcsUrl } from '@nextcloud/router'
import BrowserStorage from './BrowserStorage'
import store from '../store/index'

/**
* Sets the attachment folder setting for the user
Expand Down Expand Up @@ -65,8 +67,21 @@ const setSIPSettings = async function(sipGroups, sharedSecret, dialInInfo) {
})
}

const setPlaySounds = async function(enabled) {
const savableValue = enabled ? 'yes' : 'no'
if (store.getters.userId) {
return axios.post(generateOcsUrl('apps/spreed/api/v1/settings', 2) + 'user', {
key: 'play_sounds',
value: savableValue,
})
} else {
BrowserStorage.setItem('play_sounds', savableValue)
}
}

export {
setAttachmentFolder,
setReadStatusPrivacy,
setSIPSettings,
setPlaySounds,
}
2 changes: 2 additions & 0 deletions src/store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import participantsStore from './participantsStore'
import quoteReplyStore from './quoteReplyStore'
import settingsStore from './settingsStore'
import sidebarStore from './sidebarStore'
import soundsStore from './soundsStore'
import talkHashStore from './talkHashStore'
import tokenStore from './tokenStore'
import windowVisibilityStore from './windowVisibilityStore'
Expand All @@ -55,6 +56,7 @@ export default new Store({
quoteReplyStore,
settingsStore,
sidebarStore,
soundsStore,
talkHashStore,
tokenStore,
windowVisibilityStore,
Expand Down
65 changes: 65 additions & 0 deletions src/store/soundsStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* @copyright Copyright (c) 2019 Joas Schilling <[email protected]>
*
* @author Joas Schilling <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { loadState } from '@nextcloud/initial-state'
import BrowserStorage from '../services/BrowserStorage'

const state = {
playSoundsUser: loadState('spreed', 'play_sounds'),
playSoundsGuest: BrowserStorage.getItem('play_sounds') !== 'no',
}

const getters = {
playSounds: (state) => {
if (state.userId) {
return state.playSoundsUser
}
return state.playSoundsGuest
},
}

const mutations = {
/**
* Set play sounds
*
* @param {object} state current store state
* @param {boolean} enabled Whether sounds should be played
*/
setPlaySounds(state, enabled) {
state.playSoundsUser = enabled
state.playSoundsGuest = enabled
},
}

const actions = {
/**
* Set the actor from the current user
*
* @param {object} context default store context;
* @param {boolean} enabled Whether sounds should be played
*/
setPlaySounds({ commit }, enabled) {
commit('setPlaySounds', enabled)
},
}

export default { state, mutations, getters, actions }
141 changes: 141 additions & 0 deletions src/utils/sounds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/**
* @copyright Copyright (c) 2020 Joas Schilling <[email protected]>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

import { generateFilePath } from '@nextcloud/router'
import store from '../store'

export const Sounds = {
BLOCK_SOUND_TIMEOUT: 5000,

isInCall: false,
lastPlayedJoin: 0,
lastPlayedLeave: 0,
playedWaiting: 0,
backgroundAudio: null,
backgroundInterval: null,

_playSounceOnce(soundFile) {
const file = generateFilePath('spreed', 'img', soundFile)
const audio = new Audio(file)
audio.volume = 0.75
audio.play()
},

async playWaiting() {
if (!store.getters.playSounds) {
return
}

if (!this.backgroundAudio) {
console.debug('Loading waiting sound')
const file = generateFilePath('spreed', 'img', 'LibremPhoneCall.ogg')
this.backgroundAudio = new Audio(file)
this.backgroundAudio.volume = 0.5
}

console.debug('Playing waiting sound')
this.backgroundAudio.play()

this.playedWaiting = 0
this.backgroundInterval = setInterval(() => {
if (!store.getters.playSounds) {
return
}

console.debug('Playing waiting sound')
this.backgroundAudio.play()
this.playedWaiting++

if (this.playedWaiting >= 3) {
// Played 3 times, so we stop now.
clearInterval(this.backgroundInterval)
}
}, 15000)
},

async playJoin(force, playWaitingSound) {
clearInterval(this.backgroundInterval)

if (!store.getters.playSounds) {
return
}

if (force) {
this.isInCall = true
} else if (!this.isInCall) {
return
}

const currentTime = (new Date()).getTime()
if (!force && this.lastPlayedJoin >= (currentTime - this.BLOCK_SOUND_TIMEOUT)) {
if (this.lastPlayedJoin >= (currentTime - this.BLOCK_SOUND_TIMEOUT)) {
console.debug('Skipping join sound because it was played %.2f seconds ago', currentTime - this.lastPlayedJoin)
}
return
}

if (force) {
console.debug('Playing join sound because of self joining')
} else {
this.lastPlayedJoin = currentTime
console.debug('Playing join sound')
}

if (playWaitingSound) {
await this.playWaiting()
} else {
this._playSounceOnce('LibremEmailNotification.ogg')
}
},

async playLeave(force, playWaitingSound) {
clearInterval(this.backgroundInterval)

if (!store.getters.playSounds) {
return
}

if (!this.isInCall) {
return
}

const currentTime = (new Date()).getTime()
if (!force && this.lastPlayedLeave >= (currentTime - this.BLOCK_SOUND_TIMEOUT)) {
if (this.lastPlayedLeave >= (currentTime - this.BLOCK_SOUND_TIMEOUT)) {
console.debug('Skipping leave sound because it was played %f.2 seconds ago', currentTime - this.lastPlayedLeave)
}
return
}

if (force) {
console.debug('Playing leave sound because of self leaving')
this.isInCall = false
} else {
console.debug('Playing leave sound')
}
this.lastPlayedLeave = currentTime

this._playSounceOnce('LibremTextMessage.ogg')

if (playWaitingSound) {
this.playWaiting()
}
},
}
Loading