@speechkit/speechkit-audio-player
Version:
A web player component that can play audio from https://speechkit.io
285 lines (244 loc) • 10.2 kB
JavaScript
// SPEECHKIT: GHOST CMS CUSTOM INTEGRATION SCRIPT
// Version 1.0
(() => {
//hashing function (SO:7616461)
const cyrb53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ h1>>>16, 2246822507) ^ Math.imul(h2 ^ h2>>>13, 3266489909);
h2 = Math.imul(h2 ^ h2>>>16, 2246822507) ^ Math.imul(h1 ^ h1>>>13, 3266489909);
return 4294967296 * (2097151 & h2) + (h1>>>0);
};
const getIframe = () => document.getElementById('speechkit-io-iframe');
const setIframeDisplay = display => {
try {
getIframe().style.display = display;
} catch (e) {}
};
const setIframeHeight = height => {
try {
getIframe().style.height = height;
} catch (e) {}
};
const getJavascriptSrcUrl = () => {
//TODO: make better when know url
const sScriptUrl = new RegExp('speechkit-ghost\.js'),
aScripts = document.getElementsByTagName('script');
for (let i = 0; i < aScripts.length; i++) {
let sSrc = aScripts[i].getAttribute('src');
//Confirm src is for spkt javascript and return src
if (sSrc !== null && sSrc.search(sScriptUrl) !== -1) {
return sSrc;
}
}
return null;
};
const domReady = initSpeechKit => {
document.readyState === 'interactive' || document.readyState === 'complete'
? initSpeechKit()
: document.addEventListener('DOMContentLoaded', initSpeechKit);
};
const newApiRequest = (url, sPostData) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onerror = reject;
if (sPostData) {
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
xhr.send(sPostData);
} else{
xhr.open('GET', url, true);
xhr.send();
}
xhr.onreadystatechange = () => {
//everytime ready state changes (0-4), check it
if (xhr.readyState === 4) {
//if request finished & response ready (4)
if (xhr.status === 0 || xhr.status === 200) {
//then if status OK (local file || server)
const data = JSON.parse(xhr.responseText);
//parse the returned JSON string
resolve(data);
} else {
reject();
}
}
};
});
};
const createNewAudio = (oArticleContent, sSpktAPIKey, sProjectID, skBackend) => {
if(!sSpktAPIKey) {
console.log(`${String.fromCodePoint(0x1F9D0)} Speechkit Error: Speechkit API Key not found.`);
return;
}
const url = `${skBackend}/api/v3/projects/${sProjectID}/audio?api_key=${sSpktAPIKey}`;
const sPostData = JSON.stringify(oArticleContent);
return newApiRequest(url, sPostData);
};
// Use Speechkit API to check if audio exists for certain sProjectId for this sPageUrl
const checkIfAudioExists = (sPageUrl, sProjectID, skBackend) => {
const sAPIBaseUrl = skBackend + '/api/v2/projects/';
const sRequestUrl = sAPIBaseUrl + sProjectID + '/podcasts/search?url=' + encodeURIComponent(sPageUrl);
// https://staging-app.speechkit.io/api/v2/projects/2824/podcasts/search?url=http://165.22.123.203/
return newApiRequest(sRequestUrl);
};
// init function - Main functionality, run on DOM ready
const initSpeechKit = () => {
// first do page check to make sure we have an element to insert player into
const eArticleElement = getArticleElement();
if(!eArticleElement) {
console.log(`${String.fromCodePoint(0x1F9D0)} SpeechKit Error:
If you are expecting a player to appear on this page, please add the class "speechkit-container"
to an element within your template. \n The SpeechKit player will then be inserted into this element.`);
return;
}
// get parameters from src
const spktJsSource = getJavascriptSrcUrl(),
srcQueryString = spktJsSource.substring(spktJsSource.lastIndexOf('?')),
urlParams = new URLSearchParams(srcQueryString),
encodedData = urlParams.get('data'),
env = urlParams.get('env') || 'prod',
// get single param - data= btoa()
oJsonData = atob(encodedData),
oData = JSON.parse(oJsonData),
project_id = oData.projectId,
spkt_api_key = oData.sk,
ghost_content_key = oData.gk,
skBackend = (env === 'prod') ? 'https://app.speechkit.io' : 'https://staging-app.speechkit.io';
if(!project_id) {
console.log(`${String.fromCodePoint(0x1F9D0)} Spkt Error: Project ID not found.`);
return;
}
const sPageUrl = window.location.href;
// check content exists in API:
checkIfAudioExists(sPageUrl, project_id, skBackend)
.then((result) => {
// console.log('Audio Found, Loading Player', result)
//if content exists, load player:
createPlayer(project_id, eArticleElement, env);
}).catch((e) => {
//if no audio exists in Speechkit, get content from ghost and create audio
getGhostContent(ghost_content_key)
.then((oGhostContent) => {
const oArticle = oGhostContent.posts[0];
// Make sure we have html in string format for the hash
const sArticleText = String(oArticle.html); //.replace(/<[^>]+>/gm, '');
const sUniqueHash = cyrb53(sArticleText+oArticle.title+oArticle.url);
const oArticleContent = {
'body': sArticleText,
'title': oArticle.title,
'article_url': oArticle.url,
'external_id': sUniqueHash
};
// if content does not exist, create:
createNewAudio(oArticleContent, spkt_api_key, project_id, skBackend)
.then((result) => {
//if content exists, load player:
console.log('Speechkit: Audio submitted to API', result);
}).catch((e) => {
console.log(`${String.fromCodePoint(0x1F9D0)} Speechkit Error: API error creating audio ${e}`);
});
})
.catch((e) => {
console.log(`${String.fromCodePoint(0x1F9D0)} Speechkit Error: Cannot get page content via Ghost API ${e}`);
// An error occurred
});
});
};
// get specific element on the page ready to insert iframe alongside
// TODO: (may update to pass in class here)
const getArticleElement = () => {
// First look for the speechkit custom container, if present return it
const sElementClass = 'speechkit-container';
const eSpeechkit = document.getElementsByClassName(sElementClass)[0];
if(eSpeechkit) return eSpeechkit;
// If they have not added a custom container, we need to look for the post-template class next
// This is to make sure we do not try and insert a player on the index page for example
const bIsAPost = document.body.classList.contains('post-template');
// this is not a Ghost article post, so return null to exit
if(!bIsAPost) return null;
// next look for div with class post-full-content as second choice
const sContentClass = 'post-full-content';
const eContent = document.getElementsByClassName(sContentClass)[0];
if(eContent) return eContent;
// if not present then simply look for the article header and return that:
const eArticle = document.getElementsByTagName('Article')[0];
if(eArticle) {
const eHeaderTag = eArticle.getElementsByTagName('Header')[0];
if(eHeaderTag) return eHeaderTag;
}
// next look for div with class post-full-content as second choice
const eContentDiv = document.getElementsByClassName('content')[0];
if(eContentDiv) return eContentDiv;
//otherwise exit as we cannot find suitable element to add player
return null;
};
const updateAttributes = data => {
const iframe = getIframe();
Object.keys(data.attrs).forEach(attr => {
if (iframe && data.attrs[attr]) {
iframe.setAttribute(attr, data.attrs[attr]);
}
});
};
// Use ghost content API to fetch html content for current page
const getGhostContent = sContentAPIKey => {
if(!sContentAPIKey) {
console.log(`${String.fromCodePoint(0x1F9D0)}, 'Speechkit Error: Ghost Content API Key not found.`);
}
const sGhostUrl = window.location.origin;
const sPageUrl = window.location.pathname.replace(/\//g,''); //slug, eg: /article-about-this/
const sGhostPath = `${sGhostUrl}/ghost/api/v2/content/posts/slug/${sPageUrl}/?key=${sContentAPIKey}`;
return newApiRequest(sGhostPath);
};
// create player iframe below eArticleElement, with audio for sProjectID
const createPlayer = (sProjectID, eArticleElement, env) => {
const sDomain = (env === 'prod') ? 'https://spkt.io' : 'https://staging.spkt.io';
const src = `${sDomain}/r/${sProjectID}`;
const eIframe = document.createElement('iframe');
eIframe.style.display = 'none';
eIframe.src = src;
eIframe.id = 'speechkit-io-iframe';
eIframe.allowfullscreen = 'false';
eIframe.frameborder = '0';
eIframe.scrolling = 'no';
// iframe.style = 'display: none';
if(eArticleElement.tagName === 'HEADER') {
eArticleElement.insertAdjacentElement('afterend', eIframe);
} else {
eArticleElement.prepend(eIframe);
}
};
window.addEventListener(
'message',
e => {
if (process.env.ORIGINS.indexOf(e.origin) === -1) return;
const key = e.message ? 'message' : 'data';
const data = e[key];
// return if no data present in PM
if(!data) return;
if (data.attrs) {
if (data.msg && data.msg === 'iframe-resize') {
setIframeHeight(data.attrs.height)
}
//append margin-bottom custom styling
if(data.attrs.style) {
data.attrs.style += 'margin-bottom:15px!important;'
}
updateAttributes(data);
}
if (data === 'sk-fail') {
setIframeDisplay('none');
} else if (data === 'sk-success') {
setIframeDisplay('block');
}
},
false
);
// run init script on domReady event
domReady(initSpeechKit);
})();