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
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>
|