Forum Discussion
Have Closed Captions generate as a variable
As Phil mentioned, the recommended approach is to create your own custom text variable in Storyline and then extract the captions directly from the caption container in real time as they appear.
At the moment, there isn’t a built-in Storyline variable that exposes caption text (as far as I know). However, you can ensure captions are enabled either through the player settings or by using a trigger such as “Set Player.DisplayCaptions to True” when the timeline starts.
From there, the remaining part can be handled with JavaScript. By observing the caption container in the DOM, you can capture the caption text whenever it appears or changes and pass it into a Storyline variable.
Here’s an example:
Hello Nedim . I hope you're well. 🤚 I’m interested in this possibility (having subtitles in a text variable to display them in speech bubbles, for example). I tried to do it (as it seems you managed to do) with the AI assistant, but of course, neither it nor I succeeded! I did, however, guide it by showing it the ‘right’ method using the last sentence of your last paragraph (and after creating a text variable and setting ‘Player.DisplayCaptions’ to TRUE). Do you have any more tips to help me assist the Assistant? Please.
Also, I haven’t managed to correctly retrieve “currenttime” and “totaltime” as in your example, which could be useful. I’ve made several requests to the Assistant, created similar SL variables and tried to retrieve the existing values (in the Player??), but I’ve had just as many failures in getting it right.
Finally, do you think it’s possible NOT to display the subtitles if they’re correctly entered into a Storyline variable that’s already displayed??
Thanks in advance. Looking forward to your brilliant replies. Thierry
- Nedim21 days agoCommunity Member
Hello ThierryEMMANUEL
I was initially disappointed with the AI Assistant in Storyline. I tried it through the trial version, but I don’t use it anymore. My expectations were high. I assumed it would understand the Storyline environment, including its JavaScript API integration and DOM structure, and generate reliable code within that context.
In practice, results were inconsistent. For more complex code, it often required multiple iterations, and even then the output was frequently redundant or overly verbose. In many cases, the same functionality could be achieved with simpler, more maintainable code. When the generated code failed, I had to rely on external LLMs anyway, which reduced its overall usefulness.
That said, the Assistant can still be helpful for beginners, especially for simpler tasks like basic animations. However, many of those can also be implemented directly using Storyline triggers. Anyway, building more complex animations or manipulating the DOM structure and appearance can be time-consuming, and is often more efficiently handled with other LLMs. If time isn’t a priority, experimenting with it can still be useful and even enjoyable.
I was impressed with your use of "observer" that I used unintentionally in my last paragraph. It is partial answer to your question "Do you have any more tips to help me assist the Assistant?". Prompting matters: precise JavaScript terminology improves results. Be explicit with element access (object('id') or DOM queries) and reference common methods like addEventListener, setTimeout, setInterval, map, filter, observer etc or even different Web APIs.For example, suppose you have six rectangles on your slide. Your initial prompt might be: “Paint all odd rectangles blue.” Let me know how many iterations it took, or if the Assistant got it right at all.
A more precise prompt could be: “Fill all odd rectangles blue by selecting <path> elements inside elements with a data-model-id attribute. Use JavaScript array methods like filter, forEach, or map to identify and apply the fill.” I’m confident the Assistant would perform much better with this revised prompt.
My approach is to first write a basic script with the elements and methods I expect will work, then refine, simplify, and iterate in the right direction using tools like Gemini or ChatGPT.
Now, the code. I had already implemented a similar solution using a MutationObserver. After reading your post, I tried the same approach by asking the Assistant to generate code using the observer method, mainly to compare results. The Assistant did produce working code, but it was unnecessarily long (around 100 lines), whereas my version was significantly shorter and more efficient.
My code:(function () { const container = document.querySelector('.caption-container'); container.style.visibility = 'hidden'; // added for Thierry function getCaptionText() { const p = container.querySelector('.caption div p'); return p ? p.textContent.trim() : ""; } function updateCaption() { const text = getCaptionText(); console.log("Caption:", text); setVar("Captions", text); } const observer = new MutationObserver(() => { updateCaption(); }); observer.observe(container, { childList: true, subtree: true, characterData: true }); })();Finally, do you think it’s possible NOT to display the subtitles if they’re correctly entered into a Storyline variable that’s already displayed??
Yes, this is achieved by applying visibility: hidden to the caption container, which keeps the element in the DOM while making it invisible—unlike display: none, which removes it from the layout entirely and can cause the script to malfunction, as the container becomes inaccessible.
I hope this at least partially answers your questions about the Assistant and the caption-related script.- ThierryEMMANUEL19 days agoCommunity Member
Thank you very much for your reply, Nedim I’m learning a lot. Needless to say, I was also very disappointed with this AI assistant when it came to generating JS code to improve triggers. It clearly has no idea how SL works.
The same goes for basic animations. You may have seen my challenge #533 where I tried to animate an object with a simple circular path. Six hours later, I gave up (compared to one minute to do it with a basic path movement).
And no, it’s not useful for helping beginners because you need to know more than the assistant does to be able to assist it.
Prompting matters: precise JavaScript terminology improves results. Of course!! Be explicit with element access (object(“id”) or DOM queries) and reference common methods like addEventListener, setTimeout, setInterval, map, filter, observer etc. or even different Web APIs. I only understand one word in two. I need to work on that right now.
I thought the AI assistant used ChatGPT. It’s surprising to get better results with ChatGPT outside of SL. I’ll try again.
Your code (thanks for that) works perfectly as hoped. It’s a first step. I tried using it to guide the assistant towards improvements (dynamic adjustments to the size of the speech bubble containing the subtitles to adapt to the volume of text) but I’m heading for failure again. Once again, I’m probably not starting off on the right foot (due to a lack of basic knowledge) and not creating the right prompts.
Thanks also for the code to retrieve the total duration and the elapsed time. I’ve tweaked it a bit so that the display suits me.
Looking forward to catching up with you again.
- Nedim18 days agoCommunity Member
No problem ThierryEMMANUEL, any time. I’ll take a look at your challenge #533 and reply there if I come up with something.
- Nedim21 days agoCommunity Member
Now, regarding the code for the current time and total duration. I didn’t investigate deeply enough to confirm which player variables (if any) store these values as displayed on the timeline. Instead, I relied on the aria-valuetext attribute of the <input type="range"> element (see the HTML snippet below). I used a MutationObserver (again) in JavaScript to watch for changes to this attribute and extract the displayed time from it. That was enough for me, as I was satisfied with the result and didn’t need to chase variables.
HTML<input tabindex="0" type="range" aria-hidden="true" aria-label="slide progress" data-ref="progressBar" data-tabindex="0" max="30667" step="100" aria-valuemin="0" aria-valuemax="30667" aria-valuenow="18900" aria-valuetext="0:18 / 0:30 played">JS
const progress = document.querySelector('[data-ref="progressBar"]'); const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === "attributes" && mutation.attributeName === "aria-valuetext") { let text = progress.getAttribute("aria-valuetext"); text = text.replace(" played", ""); setVar("TimeDisplay", text); } } }); observer.observe(progress, { attributes: true, attributeFilter: ["aria-valuetext"] });
Why MutationObserver in both scripts and what is it?MutationObserver is a JavaScript API that watches for changes in the DOM and runs a callback when something updates. In both scripts (including the Captions script), I use it to listen for specific attribute changes, in this case, aria-valuetext on the progress bar, and extract the current and total time when it updates. I chose this instead of something like setInterval because it runs only when the value actually changes, rather than executing every few milliseconds unnecessarily.
The biggest benefit of both scripts is that these hidden Storyline engine values can now be fully customized and styled, since they are extracted into a custom variable and displayed in a single text box or a shape.