PageSourceSearch

https://globaljournals.org/core/modules/167ebdfa1e/assets/js/speech.js?rnd=90998

js globaljournals.org collected 2026-10-01 11:47:07 UTC 6,457 bytes, 205 lines download raw bytes

1/**
2 * GJ Persistent TTS - Frontend Playback
3 * Flow: Cache check → Play → If broken URL → auto-retry with ?force=1 → Generate fresh → Play
4 */
5(function ($) {
6  "use strict";
7
8  if (typeof gj_tts_params === "undefined") return;
9
10  var currentAudio = null;
11  var isSpeaking = false;
12  var activeButton = null;
13  var retryAttempted = false; // prevent infinite retry loop
14
15  /* -------------------------------------------------------
16   * AUDIO CONTROL
17   * ----------------------------------------------------- */
18  function playAudio(url, button) {
19    console.log("GJ TTS: Attempting to play URL:", url);
20
21    if (!url || typeof url !== "string" || url.length < 10) {
22      console.error("GJ TTS: Invalid audio URL:", url);
23      alert("Invalid audio URL received.");
24      resetButton(button);
25      return;
26    }
27
28    if (currentAudio) {
29      currentAudio.pause();
30      currentAudio = null;
31    }
32
33    currentAudio = new Audio();
34    isSpeaking = true;
35    activeButton = button;
36
37    currentAudio.onended = function () {
38      resetButton(button);
39    };
40
41    currentAudio.onerror = function () {
42      console.error("GJ TTS: Audio not playable:", url);
43
44      // Auto-retry: if cached URL failed, retry with ?force=1 to generate fresh
45      if (!retryAttempted) {
46        console.log(
47          "GJ TTS: Cached URL broken — auto-retrying with force=1...",
48        );
49        retryAttempted = true;
50        setButtonState(button, "loading");
51        fetchAudio(button, true); // force=1
52      } else {
53        alert("Audio could not be played. Please try again later.");
54        resetButton(button);
55      }
56    };
57
58    currentAudio.oncanplaythrough = function () {
59      setButtonState(button, "playing");
60      currentAudio.play().catch(function (err) {
61        console.error("GJ TTS: Play failed:", err);
62        resetButton(button);
63      });
64    };
65
66    currentAudio.src = url;
67    currentAudio.load();
68  }
69
70  /* -------------------------------------------------------
71   * BUTTON STATES
72   * ----------------------------------------------------- */
73  function setButtonState(button, state) {
74    if (!button) return;
75    if (!button.dataset.originalHtml) {
76      button.dataset.originalHtml = button.innerHTML;
77    }
78
79    button.classList.remove("speech-active", "speech-loading");
80    button.style.background = "";
81
82    if (state === "loading") {
83      button.classList.add("speech-loading");
84      button.innerHTML =
85        '<svg width="17" height="17" viewBox="0 0 24 24" fill="none" style="animation:spin 1s linear infinite"><circle cx="12" cy="12" r="10" stroke="white" stroke-width="2" stroke-dasharray="30 70"/></svg> Generating...';
86      button.style.background = "#555";
87    } else if (state === "playing") {
88      button.classList.add("speech-active");
89      button.innerHTML =
90        '<svg width="17" height="17" viewBox="0 0 24 24" fill="white"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg> Stop';
91      button.style.background = "#e74c3c";
92    }
93  }
94
95  function resetButton(button) {
96    isSpeaking = false;
97    retryAttempted = false;
98    if (button) {
99      button.classList.remove("speech-active", "speech-loading");
100      button.innerHTML = button.dataset.originalHtml || "Listen";
101      button.style.background = "";
102    }
103    if (currentAudio) {
104      currentAudio.pause();
105      currentAudio = null;
106    }
107    activeButton = null;
108  }
109
110  function stop() {
111    if (activeButton) resetButton(activeButton);
112  }
113
114  /* -------------------------------------------------------
115   * API CALL — Fetch audio URL from REST endpoint
116   * force=true skips cache and generates fresh audio
117   * ----------------------------------------------------- */
118  function fetchAudio(button, force) {
119    var apiUrl = gj_tts_params.rest_url + "get-audio/" + gj_tts_params.post_id;
120    if (force) apiUrl += "?force=1";
121
122    $.ajax({
123      url: apiUrl,
124      method: "GET",
125      headers: { "X-WP-Nonce": gj_tts_params.nonce },
126      success: function (response) {
127        console.log(
128          "GJ TTS: API response" + (force ? " (FORCED)" : "") + ":",
129          JSON.stringify(response),
130        );
131        if (response && response.status === "success" && response.url) {
132          console.log(
133            "GJ TTS: source=" +
134              (response.source || "?") +
135              " cached=" +
136              response.cached +
137              " url=" +
138              response.url,
139          );
140          playAudio(response.url, button);
141        } else {
142          var msg =
143            response && response.message
144              ? response.message
145              : "Audio generation failed.";
146          console.error("GJ TTS Error:", msg);
147          alert(msg);
148          resetButton(button);
149        }
150      },
151      error: function (xhr, status, error) {
152        var serverMsg = "";
153        try {
154          var json = JSON.parse(xhr.responseText);
155          serverMsg = json.message || "";
156        } catch (e) {
157          serverMsg = xhr.responseText
158            ? xhr.responseText.substring(0, 200)
159            : error;
160        }
161        console.error("GJ TTS: API Error |", status, "|", serverMsg);
162        alert("Audio load failed: " + serverMsg);
163        resetButton(button);
164      },
165    });
166  }
167
168  /* -------------------------------------------------------
169   * MAIN — Called on button click
170   * ----------------------------------------------------- */
171  function readSpeechSection(button) {
172    if (isSpeaking) {
173      stop();
174      return;
175    }
176
177    if (!gj_tts_params.post_id || !gj_tts_params.rest_url) {
178      console.error("GJ TTS: Missing params");
179      return;
180    }
181
182    retryAttempted = false;
183    setButtonState(button, "loading");
184    fetchAudio(button, false); // try cache first
185  }
186
187  /* -------------------------------------------------------
188   * STABILIZER & GLOBAL
189   * ----------------------------------------------------- */
190  document.addEventListener("visibilitychange", function () {
191    if (document.visibilityState === "hidden") stop();
192  });
193  window.addEventListener("pagehide", stop);
194
195  var style = document.createElement("style");
196  style.textContent = "@keyframes spin { to { transform: rotate(360deg); } }";
197  document.head.appendChild(style);
198
199  $(document).on("click", "#gj-listen-btn, .gj-listen-btn", function () {
200    readSpeechSection(this);
201  });
202
203  window.readSpeechSection = readSpeechSection;
204  window.stopSpeech = stop;
205})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.