<commit_before><!--
  - @copyright Copyright (c) 2021 <NAME> <<EMAIL>>
  -
  - @author <NAME> <<EMAIL>>
  -
  - @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/>.
-->

<template>
	<div
		class="audio-recorder">
		<button
			v-if="!isRecording"
			v-tooltip.auto="{
				content: startRecordingTooltip,
				delay: tooltipDelay,
			}"
			class="audio-recorder__trigger nc-button nc-button__main"
			@click="start">
			<Microphone
				:size="16"
				title=""
				decorative />
		</button>
		<div v-else class="wrapper">
			<button
				v-tooltip.auto="{
					content: abortRecordingTooltip,
					delay: tooltipDelay,
				}"
				class="audio-recorder__stop nc-button nc-button__main"
				@click="abortRecording">
				<Close
					:size="16"
					title=""
					decorative />
			</button>
			<div class="audio-recorder__info">
				<div class="recording-indicator fadeOutIn" />
				<span
					class="time">
					{{ parsedRecordTime }}</span>
			</div>
			<button
				v-tooltip.auto="{
					content: stopRecordingTooltip,
					delay: tooltipDelay,
				}"
				class="audio-recorder__trigger nc-button nc-button__main"
				:class="{'audio-recorder__trigger--recording': isRecording}"
				@click="stop">
				<Check
					:size="16"
					title=""
					decorative />
			</button>
		</div>
	</div>
</template>

<script>
import Microphone from 'vue-material-design-icons/Microphone'
import Close from 'vue-material-design-icons/Close'
import Check from 'vue-material-design-icons/Check'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { mediaDevicesManager } from '../../../utils/webrtc/index'
import { showError } from '@nextcloud/dialogs'

export default {
	name: 'AudioRecorder',

	components: {
		Microphone,
		Close,
		Check,
	},

	directives: {
		tooltip: Tooltip,
	},

	data() {
		return {
			// The audio stream object
			audioStream: null,
			// The media recorder which generate the recorded chunks
			mediaRecorder: null,
			// The chunks array
			chunks: [],
			// The final audio file blob
			blob: null,
			// Switched to true if the recording is aborted
			aborted: false,
			// recordTimer
			recordTimer: null,
			// the record timer
			recordTime: {
				minutes: 0,
				seconds: 0,
			},
		}
	},

	computed: {
		// Recording state of the mediaRecorder
		isRecording() {
			if (this.mediaRecorder) {
				return this.mediaRecorder.state === 'recording'
			} else {
				return false
			}
		},

		parsedRecordTime() {
			const seconds = this.recordTime.seconds.toString().length === 2 ? this.recordTime.seconds : `0${this.recordTime.seconds}`
			const minutes = this.recordTime.minutes.toString().length === 2 ? this.recordTime.minutes : `0${this.recordTime.minutes}`
			return `${minutes}:${seconds}`
		},

		tooltipDelay() {
			return { show: 500, hide: 200 }
		},

		startRecordingTooltip() {
			return t('spreed', 'Record voice message')
		},

		stopRecordingTooltip() {
			return t('spreed', 'End recording and send')
		},

		abortRecordingTooltip() {
			return t('spreed', 'Dismiss recording')
		},
	},

	watch: {

		isRecording(newValue) {
			console.debug('isRecording', newValue)
		},
	},

	methods: {
		/**
		 * Initialize the media stream and start capturing the audio
		 */
		async start() {
			// Create new audio stream
			try {
				this.audioStream = await mediaDevicesManager.getUserMedia({
					audio: true,
				})
			} catch (exception) {
				console.debug(exception)
				if (exception.name === 'NotAllowedError') {
					showError(t('spreed', 'Access to the microphone was denied'))
				} else {
					showError(t('spreed', 'Microphone either not available or disabled in settings'))
				}
				return
			}

			// Create a mediarecorder to capture the stream
			try {
				this.mediaRecorder = new MediaRecorder(this.audioStream)
			} catch (exception) {
				console.debug(exception)
				this.audioStream.getTracks().forEach(track => track.stop())
				this.audioStream = null
				showError(t('spreed', 'Error while recording audio'))
				return
			}

			// Add event handler to onstop
			this.mediaRecorder.onstop = this.generateFile

			// Add event handler to ondataAvailable
			this.mediaRecorder.ondataavailable = (e) => {
				this.chunks.push(e.data)
			}

			try {
				// Start the recording
				this.mediaRecorder.start()
			} catch (exception) {
				console.debug(exception)
				this.aborted = true
				this.stop()
				this.resetComponentData()
				showError(t('spreed', 'Error while recording audio'))
				return
			}

			console.debug(this.mediaRecorder.state)

			// Start the timer
			this.recordTimer = setInterval(() => {
				if (this.recordTime.seconds === 59) {
					this.recordTime.minutes++<commit_msg>Fix recording of voice messages on Safari<commit_after><!--
  - @copyright Copyright (c) 2021 <NAME> <<EMAIL>>
  -
  - @author <NAME> <<EMAIL>>
  -
  - @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/>.
-->

<template>
	<div
		class="audio-recorder">
		<button
			v-if="!isRecording"
			v-tooltip.auto="{
				content: startRecordingTooltip,
				delay: tooltipDelay,
			}"
			class="audio-recorder__trigger nc-button nc-button__main"
			@click="start">
			<Microphone
				:size="16"
				title=""
				decorative />
		</button>
		<div v-else class="wrapper">
			<button
				v-tooltip.auto="{
					content: abortRecordingTooltip,
					delay: tooltipDelay,
				}"
				class="audio-recorder__stop nc-button nc-button__main"
				@click="abortRecording">
				<Close
					:size="16"
					title=""
					decorative />
			</button>
			<div class="audio-recorder__info">
				<div class="recording-indicator fadeOutIn" />
				<span
					class="time">
					{{ parsedRecordTime }}</span>
			</div>
			<button
				v-tooltip.auto="{
					content: stopRecordingTooltip,
					delay: tooltipDelay,
				}"
				class="audio-recorder__trigger nc-button nc-button__main"
				:class="{'audio-recorder__trigger--recording': isRecording}"
				@click="stop">
				<Check
					:size="16"
					title=""
					decorative />
			</button>
		</div>
	</div>
</template>

<script>
import Microphone from 'vue-material-design-icons/Microphone'
import Close from 'vue-material-design-icons/Close'
import Check from 'vue-material-design-icons/Check'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import { mediaDevicesManager } from '../../../utils/webrtc/index'
import { showError } from '@nextcloud/dialogs'

export default {
	name: 'AudioRecorder',

	components: {
		Microphone,
		Close,
		Check,
	},

	directives: {
		tooltip: Tooltip,
	},

	data() {
		return {
			// The audio stream object
			audioStream: null,
			// The media recorder which generate the recorded chunks
			mediaRecorder: null,
			// The chunks array
			chunks: [],
			// The final audio file blob
			blob: null,
			// Switched to true if the recording is aborted
			aborted: false,
			// recordTimer
			recordTimer: null,
			// the record timer
			recordTime: {
				minutes: 0,
				seconds: 0,
			},
		}
	},

	computed: {
		// Recording state of the mediaRecorder
		isRecording() {
			if (this.mediaRecorder) {
				return this.mediaRecorder.state === 'recording'
			} else {
				return false
			}
		},

		parsedRecordTime() {
			const seconds = this.recordTime.seconds.toString().length === 2 ? this.recordTime.seconds : `0${this.recordTime.seconds}`
			const minutes = this.recordTime.minutes.toString().length === 2 ? this.recordTime.minutes : `0${this.recordTime.minutes}`
			return `${minutes}:${seconds}`
		},

		tooltipDelay() {
			return { show: 500, hide: 200 }
		},

		startRecordingTooltip() {
			return t('spreed', 'Record voice message')
		},

		stopRecordingTooltip() {
			return t('spreed', 'End recording and send')
		},

		abortRecordingTooltip() {
			return t('spreed', 'Dismiss recording')
		},
	},

	watch: {

		isRecording(newValue) {
			console.debug('isRecording', newValue)
		},
	},

	methods: {
		/**
		 * Initialize the media stream and start capturing the audio
		 */
		async start() {
			const useSafariFallback = MediaRecorder.isTypeSupported('video/mp4; codecs="mp4a.40.2"')

			// Create new audio stream
			try {
				this.audioStream = await mediaDevicesManager.getUserMedia({
					audio: true,
					video: false,
				})
			} catch (exception) {
				console.debug(exception)
				if (exception.name === 'NotAllowedError') {
					showError(t('spreed', 'Access to the microphone was denied'))
				} else {
					showError(t('spreed', 'Microphone either not available or disabled in settings'))
				}
				return
			}

			// Create a mediarecorder to capture the stream
			try {
				if (useSafariFallback) {
					this.mediaRecorder = new MediaRecorder(this.audioStream, {
						audioBitsPerSecond: 128000,
						videoBitsPerSecond: 0,
						mimeType: 'video/mp4; codecs="mp4a.40.2"',
					})
				} else {
					this.mediaRecorder = new MediaRecorder(this.audioStream)
				}
			} catch (exception) {
				console.debug(exception)
				this.audioStream.getTracks().forEach(track => track.stop())
				this.audioStream = null
				showError(t('spreed', 'Error while recording audio'))
				return
			}

			// Add event handler to onstop
			this.mediaRecorder.onstop = this.generateFile

			// Add event handler to ondataavailable
			this.mediaRecorder.ondataavailable = (e) => {
				this.chunks.push(e.data)
			}

			try {
				// Start the recording
				this.mediaRecorder.start()
			} catch (exception) {
				console.debug(exception)
				this.aborted = true
				this.stop()
				this.resetComponentData()
				showError(t('spreed', 'Error while recording audio'))
				return
			}

			console.debug(this.mediaRecorder.state)

			// Start the timer
			this.recordTimer = setInterval(() => {
				if (this.recordTime.seconds === 59) {
					this.recordTime.minutes++
{'fraction_non_alphanumeric': 0.12655244311485814, 'fraction_numerical': 0.006255099265705739, 'mean_word_length': 3.0454712137880455, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 67, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1086513', 'n_tokens_mistral': 4254, 'n_tokens_neox': 3431, 'n_words': 914}