import { LitElement, html, css, svg } from "lit"; import { customElement, property } from "lit/decorators.js"; @customElement("ritzy-player") export class HelloWorld extends LitElement { @property({ type: String }) clientName = "Ritzy"; @property({ type: String }) appId = ""; @property({ type: String }) selector = ""; // @property({ type: String }) lang = "en-US"; // TODO: add meta tag prop. here for extracting the language from // eg. @property({ type: String }) lang = navigator.language ?? "en-US"; @property({ type: String }) text = ""; @property({ type: Boolean }) initialPlay = true; @property({ type: Boolean }) playing = false; @property({ type: Number }) startTimeInMiliseconds = 0; @property({ type: Number }) endTimeInMiliseconds = 0; @property({ type: Object }) analyticsData = {}; static styles = css` svg { width: 25px; height: 25px; cursor: pointer; } `; private _isSendBeaconSupported() { return "sendBeacon" in navigator; } private _isAppIdProvided() { if (!this.appId || !Boolean(this.appId) || this.appId === "") { console.log("Please define your app id."); return false; } return true; } private _isSelectorDefined() { if (this.selector === "") { console.log("Please define your HTML wrapper selector, eg. '#ritzy'"); return false; } return true; } private svgPlayButton = svg` `; private svgPauseButton = svg` `; connectedCallback() { super.connectedCallback(); console.log(`appId: ${this.appId}`) document?.addEventListener("visibilitychange", () => { if (document.visibilityState === "hidden" && this._isAppIdProvided()) { try { this._isSendBeaconSupported() && this._sendAnalytics(); } catch (err) { throw new Error("An error occured while sending analytics data."); } } }); } private _play() { if (!this._isSelectorDefined()) { return; } const allElements = document.querySelectorAll(this.selector); this.text += Array.from(allElements).reduce( (prev, current) => (prev !== "" ? ", " : "") + current.textContent + ".", "" ); if ("speechSynthesis" in window) { const utterance = new SpeechSynthesisUtterance(this.text); utterance.lang = this.lang; utterance.rate = 1; utterance.onstart = () => { this.startTimeInMiliseconds = Date.now(); }; utterance.onend = () => { window.speechSynthesis.cancel(); this.playing = false; this.initialPlay = true; this.endTimeInMiliseconds = Math.floor( (Date.now() - this.startTimeInMiliseconds) / 1000 ); }; if (this.initialPlay) { window.speechSynthesis.cancel(); window.speechSynthesis.speak(utterance); this.playing = true; } else { window.speechSynthesis.resume(); this.playing = true; } this.initialPlay = false; this.text = ""; } } private _pause() { window.speechSynthesis.pause(); this.playing = false; } // TODO: Check if possible to move the logic into an inline web / service worker private _sendAnalytics() { const analyticsData = JSON.stringify({ date: new Date(), url: window.location.href, listeningTimeInMiliseconds: this.endTimeInMiliseconds, }); if (this.endTimeInMiliseconds > 0) { console.log(`send analytics (${this.clientName}): ${analyticsData}`); if (navigator.sendBeacon) { navigator.sendBeacon( `http://localhost:3001/apps/${this.appId}/analytics`, analyticsData ); } else { console.log( "No Beacon support detected in your browser. Please enable it, or use another browser." ); } } } render() { if ( typeof this.appId === "undefined" || this.appId === "" || !this._isAppIdProvided() || !this._isSelectorDefined() ) { return; } return this.playing ? html`${this.svgPauseButton}` : html`${this.svgPlayButton}`; } }