You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

218 lines
5.3 KiB

<script lang="ts">
import axios from 'axios';
const padDigits = (value) => {
if(value < 10){
return '0'+value;
} else {
return value;
}
}
const getMinutes = (seconds) => {
return Math.floor( seconds / 60 );
}
const getAccumulatedSeconds = (newerTime, olderTime) => {
return Math.floor((newerTime-olderTime)/1000);
}
const formatSeconds = (total) => {
const minutes = getMinutes(total);
const seconds = total - minutes*60;
return `${minutes}:${padDigits(seconds)}`;
}
export default {
data() {
return {
categories:[],
running: false,
startTime:0,
savedPreviousSeconds:0,
totalSeconds:0,
description:'',
practice_category_id:0,
comments:'',
manualHours:0,
manualMinutes:0,
manualSeconds:0,
analyser:null,
secondsToSubtract:0,
micThreshold:-1,
micLevel:0,
micThresholdExceeded:false
}
},
methods: {
formatTime(seconds){
return formatSeconds(seconds)
},
submit(event){
event.preventDefault();
const reqBody = {
description:this.description,
seconds: this.totalSeconds,
practice_category_id: this.practice_category_id
}
if(this.comments){
reqBody.comments = this.comments
}
axios.post(
import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'sessions',
reqBody
).then(()=>{
this.description = null;
this.totalSeconds = 0;
this.savedPreviousSeconds = 0;
this.comments = null;
this.secondsToSubtract = 0;
});
},
start(event){
this.startTime = Date.now();
this.running = true;
this.intervalID = setInterval(()=>{
const array = new Uint8Array(this.analyser.frequencyBinCount);
this.analyser.getByteFrequencyData(array);
const arraySum = array.reduce((a, value) => a + value, 0);
this.micLevel = arraySum / array.length;
if(this.micLevel > this.micThreshold){
this.micThresholdExceeded = true;
} else {
this.secondsToSubtract++;
this.micThresholdExceeded = false;
}
this.totalSeconds = getAccumulatedSeconds(Date.now(), this.startTime) - this.secondsToSubtract + this.savedPreviousSeconds;
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
}, 1000);
},
updateSavedPreviousSeconds(event){
this.savedPreviousSeconds = parseInt(event.target.value);
},
stop(event){
this.running = false;
this.savedPreviousSeconds += getAccumulatedSeconds(Date.now(), this.startTime);
clearInterval(this.intervalID);
},
reset(event){
this.savedPreviousSeconds = 0;
this.totalSeconds = 0;
this.secondsToSubtract = 0;
},
setSecondsManually(){
this.totalSeconds = this.manualHours*60*60 + this.manualMinutes*60 + this.manualSeconds;
this.savedPreviousSeconds = this.totalSeconds;
this.secondsToSubtract = 0;
window.localStorage.setItem('lastTotalSeconds', this.totalSeconds);
}
},
mounted() {
axios.get(import.meta.env.VITE_PRACTICE_TRACKER_API_URL+'categories').then((response)=>{
this.categories = response.data
})
navigator.mediaDevices.getUserMedia({
audio: true
}).then((stream) => {
const audioContext = new AudioContext();
this.analyser = audioContext.createAnalyser();
const microphone = audioContext.createMediaStreamSource(stream);
const scriptProcessor = audioContext.createScriptProcessor(2048, 1, 1);
this.analyser.smoothingTimeConstant = 0.8;
this.analyser.fftSize = 1024;
microphone.connect(this.analyser);
this.analyser.connect(scriptProcessor);
scriptProcessor.connect(audioContext.destination);
})
.catch((err) => {
console.error(err);
});
}
}
</script>
<template>
<h2>Timer</h2>
<div :class="{running:running, micThresholdExceeded:micThresholdExceeded}">
<em>
{{formatTime(totalSeconds)}}
</em>
Mic Threshold (current level: {{Math.round(micLevel)}}): <input type="number" v-model="micThreshold"/>
<button :disabled="running" @click="start">Start</button>
<button :disabled="!running" @click="stop">Stop</button>
<button :disabled="running || totalSeconds === 0" @click="reset">Reset</button>
</div>
<form @submit="submit">
<label>Description</label>
<input v-model="description" type="text" maxlength="128"/>
<label>Seconds</label>
<input @change="updateSavedPreviousSeconds" v-model="totalSeconds" type="number"/>
<label>Comments</label>
<textarea v-model="comments"/>
<label>Practice Category</label>
<select v-model="practice_category_id">
<option v-for="category in categories" v-bind:value="category.id">
{{category.id}}.
{{category.instrument}}
:
{{category.category}}
</option>
</select>
<input type="Submit"/>
<label>Hours:</label>
<input type="number" @change="setSecondsManually" v-model="manualHours"/>
<label>Minutes:</label>
<input type="number" @change="setSecondsManually" v-model="manualMinutes"/>
<label>Seconds:</label>
<input type="number" @change="setSecondsManually" v-model="manualSeconds"/>
</form>
</template>
<style scoped>
label, [type="submit"] {
display:block;
}
input[type="number"] {
width: 5em;
}
.running {
background:lightgreen;
}
.running.micThresholdExceeded {
background:green;
}
button, em {
margin: 1em 0;
display:block;
font-size:3em;
font-style:normal;
font-weight:bold;
width:100%;
}
select {
width: 100%;
}
</style>