From 0cecf322e5e560efb1cc0230800ec2e56ba442a9 Mon Sep 17 00:00:00 2001 From: Aritra Date: Thu, 9 May 2024 13:41:24 +0530 Subject: [PATCH 1/7] init implementation --- src/pages/LEVANTE/UserSurvey.vue | 159 ++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 3 deletions(-) diff --git a/src/pages/LEVANTE/UserSurvey.vue b/src/pages/LEVANTE/UserSurvey.vue index d2b90aace..0b6cc020d 100644 --- a/src/pages/LEVANTE/UserSurvey.vue +++ b/src/pages/LEVANTE/UserSurvey.vue @@ -11,6 +11,98 @@ import { useGameStore } from '@/store/game'; import { Converter } from 'showdown'; import { useI18n } from 'vue-i18n'; +function BufferLoader(context, urlListMap, callback) { + this.context = context; + this.urlListMap = urlListMap; + this.onload = callback; + this.bufferList = new Array(); + this.loadCount = 0; +} + +BufferLoader.prototype.loadBuffer = function(url, index) { + // Load buffer asynchronously + var request = new XMLHttpRequest(); + request.open("GET", url, true); + request.responseType = "arraybuffer"; + + var loader = this; + + request.onload = function() { + // Asynchronously decode the audio file data in request.response + loader.context.decodeAudioData( + request.response, + function(buffer) { + if (!buffer) { + alert('error decoding file data: ' + url); + return; + } + loader.bufferList[index] = buffer; + if (++loader.loadCount === Object.keys(loader.urlListMap).length) + loader.onload(loader.bufferList); + }, + function(error) { + console.error('decodeAudioData error', error); + } + ); + } + + request.onerror = function() { + // alert('BufferLoader: XHR error'); + } + + request.send(); +} + +BufferLoader.prototype.load = function() { + Object.keys(this.urlListMap).forEach((key, index) => { + this.loadBuffer(this.urlListMap[key], key); + }); +} + +const generateAudioLinks = () => { + const fileNames = [ + 'ChildSurveyIntro', + 'ClassFriends', + 'ClassHelp', + 'ClassNice', + 'ClassPlay', + 'Example1Comic', + 'Example2Neat', + 'GrowthMindMath', + 'GrowthMindRead', + 'GrowthMindSmart', + 'LearningGood', + 'LonelySchool', + 'MathEnjoy', + 'MathGood', + 'ReadingEnjoy', + 'ReadingGood', + 'SchoolEnjoy', + 'SchoolFun', + 'SchoolGiveUp', + 'SchoolHappy', + 'SchoolSafe', + 'TeacherLike', + 'TeacherListen', + 'TeacherNice', + ]; + // TODO: Make this sensitive to the language + // For now, it is hardcoded to spanish + const baseURL = 'https://storage.googleapis.com/child-questionnaire/es/shared/'; + return fileNames.reduce((acc, curr) => { + // acc[curr] = `${baseURL}${curr}.mp3`; + const rand = Math.random(); + if (rand < 0.2) { + acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene2-q3-false_belief.mp3'; + } else if (rand >= 0.2 && rand < 0.5) { + acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene2-instruct2.mp3'; + } else { + acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene3-instruct1.mp3'; + } + return acc; + }, {}); +}; + const authStore = useAuthStore(); const { roarfirekit } = storeToRefs(authStore); const fetchedSurvey = ref(null); @@ -19,18 +111,45 @@ const isSavingResponses = ref(false); const gameStore = useGameStore(); const converter = new Converter(); const { locale } = useI18n(); +const audioPlayerBuffers = ref([]); const router = useRouter(); +let AudioContext = window.AudioContext || window.webkitAudioContext; +const context = new AudioContext(); +let currentAudioSource = null; + +function finishedLoading(bufferList) { + audioPlayerBuffers.value = bufferList; +} + +const bufferLoader = new BufferLoader( + context, + generateAudioLinks(), + finishedLoading +); + +bufferLoader.load(); // Fetch the survey on component mount onMounted(async () => { + console.log('mark://', 'on getStore'); await getSurvey(); }); -async function getSurvey() { - let userType = toRaw(authStore.userData.userType.toLowerCase()); - if (userType === 'student') userType = 'child'; +const showAndPlaceAudioButton = (playAudioButton, el) => { + if (playAudioButton) { + playAudioButton.style.display = 'inline-block'; + playAudioButton.style.position = 'absolute'; + playAudioButton.style.right = 0; + playAudioButton.style.top = 0; + el.appendChild(playAudioButton); + } +}; +async function getSurvey() { + // let userType = toRaw(authStore.userData.userType.toLowerCase()); + // if (userType === 'student') userType = 'child'; + const userType = 'child'; try { const response = await axios.get(`https://storage.googleapis.com/road-dashboard/${userType}_survey.json`); fetchedSurvey.value = response.data; @@ -51,6 +170,22 @@ async function getSurvey() { }); survey.value.onComplete.add(saveResults); + survey.value.onAfterRenderPage.add((__, { htmlElement }) => { + const questionElements = htmlElement.querySelectorAll('div[id^=sq_]'); + if (questionElements[0].dataset.name !== 'ChildSurveyIntro') { + // const introButton = document.getElementById('audio-button-ChildSurveyIntro'); + // introButton.style.display = 'none'; + questionElements.forEach((el) => { + const playAudioButton = document.getElementById('audio-button-'+ el.dataset.name); + showAndPlaceAudioButton(playAudioButton, el); + }); + } else { + const introButton = document.getElementById('audio-button-ChildSurveyIntro'); + showAndPlaceAudioButton(introButton, questionElements[0]); + // console.log('mark://in intro', 'introButton', introButton); + } + + }); } catch (error) { console.error(error); } @@ -61,9 +196,21 @@ watch( () => locale.value, (newLocale) => { survey.value.locale = newLocale; + console.log(document.getElementById('sq_102')); }, ); +async function playAudio(name) { + if (currentAudioSource) { + await currentAudioSource.stop(); + } + const source = context.createBufferSource(); + currentAudioSource = source; + source.buffer = audioPlayerBuffers.value[name]; + source.connect(context.destination); + source.start(0); +} + async function saveResults(sender) { console.log('sender.data: ', sender.data); @@ -101,6 +248,12 @@ async function saveResults(sender) { From ce032a114c8b82ffe8f9e00115a38352add166cb Mon Sep 17 00:00:00 2001 From: Aritra Date: Thu, 9 May 2024 18:33:00 +0530 Subject: [PATCH 2/7] consolidating the code --- src/helpers/audio.js | 95 ++++++++++++++++++++++++++++++++ src/pages/LEVANTE/UserSurvey.vue | 79 +++++++------------------- 2 files changed, 115 insertions(+), 59 deletions(-) create mode 100644 src/helpers/audio.js diff --git a/src/helpers/audio.js b/src/helpers/audio.js new file mode 100644 index 000000000..f83b50362 --- /dev/null +++ b/src/helpers/audio.js @@ -0,0 +1,95 @@ +// export function BufferLoader(context, urlListMap, callback) { +// this.context = context; +// this.urlListMap = urlListMap; +// this.onload = callback; +// this.bufferList = new Array(); +// this.loadCount = 0; +// } + +// BufferLoader.prototype.loadBuffer = function(url, index) { +// // Load buffer asynchronously +// var request = new XMLHttpRequest(); +// request.open("GET", url, true); +// request.responseType = "arraybuffer"; + +// var loader = this; + +// request.onload = function() { +// // Asynchronously decode the audio file data in request.response +// loader.context.decodeAudioData( +// request.response, +// function(buffer) { +// if (!buffer) { +// alert('error decoding file data: ' + url); +// return; +// } +// loader.bufferList[index] = buffer; +// if (++loader.loadCount === Object.keys(loader.urlListMap).length) +// loader.onload(loader.bufferList); +// }, +// function(error) { +// console.error('decodeAudioData error', error); +// } +// ); +// } + +// request.onerror = function() { +// // alert('BufferLoader: XHR error'); +// } + +// request.send(); +// } + +// BufferLoader.prototype.load = function() { +// Object.keys(this.urlListMap).forEach((key, index) => { +// this.loadBuffer(this.urlListMap[key], key); +// }); +// } + +export class BufferLoader { + constructor(context, urlListMap, callback) { + this.context = context; + this.urlListMap = urlListMap; + this.onload = callback; + this.bufferList = new Array(); + this.loadCount = 0; + } + + loadBuffer(url, index) { + const request = new XMLHttpRequest(); + request.open("GET", url, true); + request.responseType = "arraybuffer"; + request.onload = () => { + // Asynchronously decode the audio file data in request.response + this.context.decodeAudioData( + request.response, + (buffer) => { + if (!buffer) { + alert('error decoding file data: ' + url); + return; + } + this.bufferList[index] = buffer; + if (++this.loadCount === Object.keys(this.urlListMap).length) + this.onload(this.bufferList); + }, + (error) => { + console.error('decodeAudioData error', error); + } + ); + } + + request.onerror = (error) => { + console.error('Request error', error); + } + + request.send(); + } + + load() { + Object.keys(this.urlListMap).forEach((key) => { + this.loadBuffer(this.urlListMap[key], key); + }); + } +} + +export const AudioContext = window.AudioContext || window.webkitAudioContext; diff --git a/src/pages/LEVANTE/UserSurvey.vue b/src/pages/LEVANTE/UserSurvey.vue index 0b6cc020d..fc61125ae 100644 --- a/src/pages/LEVANTE/UserSurvey.vue +++ b/src/pages/LEVANTE/UserSurvey.vue @@ -10,54 +10,7 @@ import { useRouter } from 'vue-router'; import { useGameStore } from '@/store/game'; import { Converter } from 'showdown'; import { useI18n } from 'vue-i18n'; - -function BufferLoader(context, urlListMap, callback) { - this.context = context; - this.urlListMap = urlListMap; - this.onload = callback; - this.bufferList = new Array(); - this.loadCount = 0; -} - -BufferLoader.prototype.loadBuffer = function(url, index) { - // Load buffer asynchronously - var request = new XMLHttpRequest(); - request.open("GET", url, true); - request.responseType = "arraybuffer"; - - var loader = this; - - request.onload = function() { - // Asynchronously decode the audio file data in request.response - loader.context.decodeAudioData( - request.response, - function(buffer) { - if (!buffer) { - alert('error decoding file data: ' + url); - return; - } - loader.bufferList[index] = buffer; - if (++loader.loadCount === Object.keys(loader.urlListMap).length) - loader.onload(loader.bufferList); - }, - function(error) { - console.error('decodeAudioData error', error); - } - ); - } - - request.onerror = function() { - // alert('BufferLoader: XHR error'); - } - - request.send(); -} - -BufferLoader.prototype.load = function() { - Object.keys(this.urlListMap).forEach((key, index) => { - this.loadBuffer(this.urlListMap[key], key); - }); -} +import { BufferLoader, AudioContext } from '@/helpers/audio'; const generateAudioLinks = () => { const fileNames = [ @@ -114,7 +67,6 @@ const { locale } = useI18n(); const audioPlayerBuffers = ref([]); const router = useRouter(); -let AudioContext = window.AudioContext || window.webkitAudioContext; const context = new AudioContext(); let currentAudioSource = null; @@ -132,16 +84,13 @@ bufferLoader.load(); // Fetch the survey on component mount onMounted(async () => { - console.log('mark://', 'on getStore'); await getSurvey(); }); const showAndPlaceAudioButton = (playAudioButton, el) => { if (playAudioButton) { - playAudioButton.style.display = 'inline-block'; - playAudioButton.style.position = 'absolute'; - playAudioButton.style.right = 0; - playAudioButton.style.top = 0; + playAudioButton.classList.add('play-button-visible'); + playAudioButton.style.display = 'flex'; el.appendChild(playAudioButton); } }; @@ -173,8 +122,9 @@ async function getSurvey() { survey.value.onAfterRenderPage.add((__, { htmlElement }) => { const questionElements = htmlElement.querySelectorAll('div[id^=sq_]'); if (questionElements[0].dataset.name !== 'ChildSurveyIntro') { - // const introButton = document.getElementById('audio-button-ChildSurveyIntro'); - // introButton.style.display = 'none'; + if (currentAudioSource) { + currentAudioSource.stop(); + } questionElements.forEach((el) => { const playAudioButton = document.getElementById('audio-button-'+ el.dataset.name); showAndPlaceAudioButton(playAudioButton, el); @@ -196,7 +146,6 @@ watch( () => locale.value, (newLocale) => { survey.value.locale = newLocale; - console.log(document.getElementById('sq_102')); }, ); @@ -248,9 +197,10 @@ async function saveResults(sender) { - + From c3e70e698ace29070f9aaecfe919ff71f0651fca Mon Sep 17 00:00:00 2001 From: Aritra Date: Fri, 10 May 2024 00:09:38 +0530 Subject: [PATCH 3/7] fixing lint --- src/pages/LEVANTE/UserSurvey.vue | 79 +++++++++++++++++++------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/src/pages/LEVANTE/UserSurvey.vue b/src/pages/LEVANTE/UserSurvey.vue index fc61125ae..03cb1742d 100644 --- a/src/pages/LEVANTE/UserSurvey.vue +++ b/src/pages/LEVANTE/UserSurvey.vue @@ -12,7 +12,7 @@ import { Converter } from 'showdown'; import { useI18n } from 'vue-i18n'; import { BufferLoader, AudioContext } from '@/helpers/audio'; -const generateAudioLinks = () => { +const generateAudioLinks = (parsedLocale) => { const fileNames = [ 'ChildSurveyIntro', 'ClassFriends', @@ -39,19 +39,10 @@ const generateAudioLinks = () => { 'TeacherListen', 'TeacherNice', ]; - // TODO: Make this sensitive to the language - // For now, it is hardcoded to spanish - const baseURL = 'https://storage.googleapis.com/child-questionnaire/es/shared/'; + + const baseURL = `https://storage.googleapis.com/road-dashboard/child-survey/${parsedLocale}/shared/`; return fileNames.reduce((acc, curr) => { - // acc[curr] = `${baseURL}${curr}.mp3`; - const rand = Math.random(); - if (rand < 0.2) { - acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene2-q3-false_belief.mp3'; - } else if (rand >= 0.2 && rand < 0.5) { - acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene2-instruct2.mp3'; - } else { - acc[curr] = 'https://storage.googleapis.com/theory-of-mind/en/shared/ToM-scene3-instruct1.mp3'; - } + acc[curr] = `${baseURL}${curr}.mp3`; return acc; }, {}); }; @@ -64,23 +55,39 @@ const isSavingResponses = ref(false); const gameStore = useGameStore(); const converter = new Converter(); const { locale } = useI18n(); -const audioPlayerBuffers = ref([]); - +const audioPlayerBuffers = ref({}); +const audioLoading = ref(false); const router = useRouter(); const context = new AudioContext(); + let currentAudioSource = null; -function finishedLoading(bufferList) { - audioPlayerBuffers.value = bufferList; +function getParsedLocale() { + return (locale.value || '').split('-')?.[0] || 'en'; } -const bufferLoader = new BufferLoader( - context, - generateAudioLinks(), - finishedLoading -); +function finishedLoading(bufferList, parsedLocale) { + audioPlayerBuffers.value[parsedLocale] = bufferList; + audioLoading.value = false; +} + +// Function to fetch buffer or return from the cache +const fetchBuffer = (parsedLocale) => { + // buffer already exists for the given local + if (audioPlayerBuffers.value[parsedLocale]) { + return; + } + audioLoading.value = true; + const bufferLoader = new BufferLoader( + context, + generateAudioLinks(parsedLocale), + (bufferList) => finishedLoading(bufferList, parsedLocale) + ); + + bufferLoader.load(); +}; + -bufferLoader.load(); // Fetch the survey on component mount onMounted(async () => { @@ -96,9 +103,9 @@ const showAndPlaceAudioButton = (playAudioButton, el) => { }; async function getSurvey() { - // let userType = toRaw(authStore.userData.userType.toLowerCase()); - // if (userType === 'student') userType = 'child'; - const userType = 'child'; + let userType = toRaw(authStore.userData.userType.toLowerCase()); + if (userType === 'student') userType = 'child'; + try { const response = await axios.get(`https://storage.googleapis.com/road-dashboard/${userType}_survey.json`); fetchedSurvey.value = response.data; @@ -106,6 +113,7 @@ async function getSurvey() { const surveyInstance = new Model(fetchedSurvey.value); surveyInstance.locale = locale.value; + fetchBuffer(getParsedLocale(locale.value)); survey.value = surveyInstance; survey.value.onTextMarkdown.add(function (survey, options) { @@ -132,7 +140,6 @@ async function getSurvey() { } else { const introButton = document.getElementById('audio-button-ChildSurveyIntro'); showAndPlaceAudioButton(introButton, questionElements[0]); - // console.log('mark://in intro', 'introButton', introButton); } }); @@ -146,16 +153,22 @@ watch( () => locale.value, (newLocale) => { survey.value.locale = newLocale; + // stop any current audio playing + if (currentAudioSource) { + currentAudioSource.stop(); + } + fetchBuffer(getParsedLocale(newLocale)); }, ); async function playAudio(name) { + const currentLocale = getParsedLocale(locale.value); if (currentAudioSource) { await currentAudioSource.stop(); } const source = context.createBufferSource(); currentAudioSource = source; - source.buffer = audioPlayerBuffers.value[name]; + source.buffer = audioPlayerBuffers.value[currentLocale][name]; source.connect(context.destination); source.start(0); } @@ -195,17 +208,17 @@ async function saveResults(sender) { From d15443c78514b34c329448ccabb9407bc7b655ba Mon Sep 17 00:00:00 2001 From: Aritra Date: Fri, 10 May 2024 16:18:34 +0530 Subject: [PATCH 6/7] rewriting the fetch logic of audio links and removing some redundant logic --- src/pages/LEVANTE/UserSurvey.vue | 72 ++++++++++++-------------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/src/pages/LEVANTE/UserSurvey.vue b/src/pages/LEVANTE/UserSurvey.vue index b260379e2..f7ad65370 100644 --- a/src/pages/LEVANTE/UserSurvey.vue +++ b/src/pages/LEVANTE/UserSurvey.vue @@ -12,39 +12,22 @@ import { Converter } from 'showdown'; import { useI18n } from 'vue-i18n'; import { BufferLoader, AudioContext } from '@/helpers/audio'; -const generateAudioLinks = (parsedLocale) => { - const fileNames = [ - 'ChildSurveyIntro', - 'ClassFriends', - 'ClassHelp', - 'ClassNice', - 'ClassPlay', - 'Example1Comic', - 'Example2Neat', - 'GrowthMindMath', - 'GrowthMindRead', - 'GrowthMindSmart', - 'LearningGood', - 'LonelySchool', - 'MathEnjoy', - 'MathGood', - 'ReadingEnjoy', - 'ReadingGood', - 'SchoolEnjoy', - 'SchoolFun', - 'SchoolGiveUp', - 'SchoolHappy', - 'SchoolSafe', - 'TeacherLike', - 'TeacherListen', - 'TeacherNice', - ]; - - const baseURL = `https://storage.googleapis.com/road-dashboard/child-survey/${parsedLocale}/shared/`; - return fileNames.reduce((acc, curr) => { - acc[curr] = `${baseURL}${curr}.mp3`; - return acc; - }, {}); +const fetchAudioLinks = async (surveyType) => { + const response = await axios.get('https://storage.googleapis.com/storage/v1/b/road-dashboard/o/'); + const files = response.data || { items: [] }; + const audioLinkMap = {}; + files.items.forEach((item) => { + if (item.contentType === 'audio/mpeg' && item.name.startsWith(surveyType)) { + const splitParts = item.name.split('/'); + const fileLocale = splitParts[1]; + const fileName = splitParts.at(-1).split('.')[0]; + if (!audioLinkMap[fileLocale]) { + audioLinkMap[fileLocale] = {}; + } + audioLinkMap[fileLocale][fileName] = `https://storage.googleapis.com/road-dashboard/${item.name}`; + } + }); + return audioLinkMap; }; const authStore = useAuthStore(); @@ -59,6 +42,7 @@ const audioPlayerBuffers = ref({}); const audioLoading = ref(false); const router = useRouter(); const context = new AudioContext(); +const audioLinks = ref({}); let currentAudioSource = null; @@ -78,7 +62,7 @@ const fetchBuffer = (parsedLocale) => { return; } audioLoading.value = true; - const bufferLoader = new BufferLoader(context, generateAudioLinks(parsedLocale), (bufferList) => + const bufferLoader = new BufferLoader(context, audioLinks.value[parsedLocale], (bufferList) => finishedLoading(bufferList, parsedLocale), ); @@ -104,11 +88,14 @@ async function getSurvey() { try { const response = await axios.get(`https://storage.googleapis.com/road-dashboard/${userType}_survey.json`); + const audioLinkMap = await fetchAudioLinks('child-survey'); + audioLinks.value = audioLinkMap; fetchedSurvey.value = response.data; // Create the survey model with the fetched data const surveyInstance = new Model(fetchedSurvey.value); surveyInstance.locale = locale.value; + fetchBuffer(getParsedLocale(locale.value)); survey.value = surveyInstance; @@ -125,18 +112,13 @@ async function getSurvey() { survey.value.onComplete.add(saveResults); survey.value.onAfterRenderPage.add((__, { htmlElement }) => { const questionElements = htmlElement.querySelectorAll('div[id^=sq_]'); - if (questionElements[0].dataset.name !== 'ChildSurveyIntro') { - if (currentAudioSource) { - currentAudioSource.stop(); - } - questionElements.forEach((el) => { - const playAudioButton = document.getElementById('audio-button-' + el.dataset.name); - showAndPlaceAudioButton(playAudioButton, el); - }); - } else { - const introButton = document.getElementById('audio-button-ChildSurveyIntro'); - showAndPlaceAudioButton(introButton, questionElements[0]); + if (currentAudioSource) { + currentAudioSource.stop(); } + questionElements.forEach((el) => { + const playAudioButton = document.getElementById('audio-button-' + el.dataset.name); + showAndPlaceAudioButton(playAudioButton, el); + }); }); } catch (error) { console.error(error); From 3487232329020dae8c5f4edd406a046078a1d5cc Mon Sep 17 00:00:00 2001 From: Aritra Date: Fri, 10 May 2024 20:22:16 +0530 Subject: [PATCH 7/7] changing the preincrement to more readable format --- src/helpers/audio.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers/audio.js b/src/helpers/audio.js index 8a4fe435b..e93221b98 100644 --- a/src/helpers/audio.js +++ b/src/helpers/audio.js @@ -21,7 +21,8 @@ export class BufferLoader { return; } this.bufferList[index] = buffer; - if (++this.loadCount === Object.keys(this.urlListMap).length) this.onload(this.bufferList); + this.loadCount += 1; + if (this.loadCount === Object.keys(this.urlListMap).length) this.onload(this.bufferList); }, (error) => { console.error('decodeAudioData error', error);