Forum Discussion
Embedded Videos and Locked Navigation
I appreciate that suggestion, and I did test adding a trigger but because the video is embedded using Insert > Video > Video from Website there is no way for Storyline to know when the embedded video has completed. I added parameters to the embed code to start/stop the video where they wanted it, but that isn't communicated or connected to the trigger.
By using Insert > Video > Video from Website, you can embed a YouTube video, but the embedded player does not expose the YouTube IFrame API, so events such as ENDED cannot be detected directly.
A more flexible approach is to embed the video through a Web Object that loads a custom index.html. This allows you to initialize the YouTube IFrame API, listen for the player's onStateChange event, and detect when the video reaches the ENDED state. When that event occurs, use the Storyline Player API (GetPlayer().SetVar()) to update a Storyline variable (for example, videoComplete). You can then create a Storyline trigger to enable the Next button or advance to the next slide when that variable changes.
Alternatively, if you want the Next button to be disabled each time the learner revisits the slide, disable it when the timeline starts and enable it only after the video reaches the ENDED state. This ensures the learner must watch the video to completion on every visit before the Next button becomes available.
Example HTML and demonstration are provided below.
<div id="player"></div>
<script src="https://www.youtube.com/iframe_api"></script>
<script>
function onYouTubeIframeAPIReady() {
new YT.Player("player", {
width: "100%",
height: "100%",
videoId: "taIdxEH_Kus",
events: {
onStateChange(e) {
if (e.data === YT.PlayerState.ENDED) {
window.parent.GetPlayer().SetVar("videoComplete", true);
//optionally enable the next button when the video is complete
const next = window.parent.document.querySelector('#next');
next.classList.remove('cs-disabled');
}
}
}
});
}
</script>- randykepple1 month agoCommunity Member
Everyone has been very helpful and I appreciate the insights and information everyone has shared. Nedim, what you are suggesting is something I haven't used, so this will be new for me. What you are saying about the API makes perfect sense and I would like to test your approach.
I also thought I'd share the embed code that I've been working with in case it's helpful in recommending a solution, but also to anyone in the future who may have a similar issue they are troubleshooting.
<iframe width="960" height="540" src="https://www.youtube.com/embed/UzFblFnvcGU?autoplay=1&controls=0&start=30&end=170" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen> </iframe>I found this support article that mentions right-clicking the Web Object Placeholder to access the ability to edit:
https://www.articulatesupport.com/article/Storyline-360-Editing-Web-Objects
Not having done this before, I'm learning as I go. It appears that I need to save that html script as an index.html document in a folder. Where I'm fuzzy on comprehension is whether this method is a one-and-done to embed it into my Storyline course? Does that folder and html file need to be kept with the project file, or is it just needed until the project is published?
I've never had to delve into the world of scripting for a project, so I appreciate the opportunity to learn how to solve this unique challenge. I love learning new skills and methods to make training better for the learner!
- Nedim1 month agoCommunity Member
I recommend reading Articulate's documentation on Web Objects, as it explains the feature more thoroughly than I can in a message. The implementation is fairly straightforward.
The key requirement is that your web content is contained in a folder with an index.html file as its entry point. I recommend keeping this folder alongside your .story file so it is always included with the project when it is moved or archived.
To insert the Web Object, add a Web Object to your slide, then right-click it and select Web Object Edit. Browse to the folder containing your index.html file, select the folder (not the file itself), and click OK. Storyline will automatically recognize and use index.html as the entry point.
One limitation of this approach is that Web Objects cannot be previewed within Storyline's Preview mode. To test the functionality, you'll need to publish the project to Web, Review 360, or LMS.
If your index.html communicates with Storyline by setting Storyline variables through the JavaScript API, it may not work when opening the published content directly from the local file system due to browser cross-origin (CORS) and security restrictions. For local testing, you should serve the files through a local web server (for example, using Visual Studio Code with Live Server or another local HTTP server such as http://127.0.0.1). Alternatively, publishing to Review 360 or an LMS also provides an appropriate hosting environment.
If you make any changes to the HTML, JavaScript, or other assets in the Web Object index.html, I recommend renaming the Web Object folder before importing it into Storyline again. For example, if the original folder is named YouTube01, rename it to YouTube02. This helps ensure that Storyline imports the updated files instead of using a previously cached version of the Web Object. Otherwise, your changes may not be reflected after republishing.
I've attached a ZIP archive containing:
- a sample Storyline project (.story), and
- the Web Object folder containing the index.html file.
The sample demonstrates embedding a YouTube video and setting the Storyline variable videoComplete to true when playback reaches 170 seconds. That variable is then used to enable the Next button, which is initially disabled on the same slide.
The HTML implementation is shown below:
<div id="player"></div> <script src="https://www.youtube.com/iframe_api"></script> <script> var player; function onYouTubeIframeAPIReady() { player = new YT.Player('player', { height: '100%', width: '100%', videoId: 'UzFblFnvcGU', playerVars: { 'autoplay': 1, 'controls': 0, 'start': 30, 'end': 170, 'frameborder': 0 }, events: { 'onStateChange': onPlayerStateChange } }); } function onPlayerStateChange(event) { if (event.data === YT.PlayerState.ENDED) { window.parent.GetPlayer().SetVar("video01Complete", true); } } </script>OPTION 2:
To avoid using an external Web Object and a separate index.html file, you can implement a more advanced JavaScript-based solution that embeds the YouTube player directly into a standard Storyline shape.
The script identifies the target rectangle using either a custom accessibility label through the data-acc-text attribute or the shape’s unique data-model-id. It then dynamically creates a YouTube iframe player and continuously synchronizes its position and dimensions with the Storyline object, ensuring that the video remains precisely aligned within the rectangle.
Because the solution uses the YouTube IFrame Player API, it can also monitor playback events. When the video reaches the defined endpoint, the script updates a Storyline variable, allowing you to enable navigation, display additional content, or mark the interaction as complete.
The implementation is relatively easy to customize once you know which lines define the YouTube video ID, start time, end time, autoplay behavior, controls, and Storyline variable name. This solution can be tested on Slide 2 of the attached Storyline project file.
const target = document.querySelector('[data-acc-text="youtubeContainer"]'); if (target) { // Remove previous player when revisiting the slide if (window.youtubePlayer?.destroy) { window.youtubePlayer.destroy(); } document.getElementById("youtubeVideo")?.remove(); // Reset Storyline variable GetPlayer().SetVar("video02Complete", false); // Create the player container const video = document.createElement("div"); video.id = "youtubeVideo"; Object.assign(video.style, { position: "fixed", zIndex: "1000", overflow: "hidden", borderRadius: "20px" }); document.body.appendChild(video); // Keep the video aligned with the Storyline rectangle const positionVideo = () => { const rect = target.getBoundingClientRect(); Object.assign(video.style, { left: `${rect.left}px`, top: `${rect.top}px`, width: `${rect.width}px`, height: `${rect.height}px` }); }; positionVideo(); window.addEventListener("resize", positionVideo); window.youtubeResizeObserver?.disconnect(); window.youtubeResizeObserver = new ResizeObserver(positionVideo); window.youtubeResizeObserver.observe(target); // Create YouTube player const createYouTubePlayer = () => { window.youtubePlayer = new YT.Player("youtubeVideo", { videoId: "UzFblFnvcGU", playerVars: { autoplay: 1, controls: 0, start: 30, end: 170, playsinline: 1, rel: 0 }, events: { onReady: event => { event.target.seekTo(30, true); event.target.playVideo(); }, onStateChange: event => { if (event.data === YT.PlayerState.ENDED) { GetPlayer().SetVar("video02Complete", true); } } } }); }; // Load YouTube API only once if (window.YT?.Player) { createYouTubePlayer(); } else { window.onYouTubeIframeAPIReady = createYouTubePlayer; if (!document.getElementById("youtubeAPI")) { const script = document.createElement("script"); script.id = "youtubeAPI"; script.src = "https://www.youtube.com/iframe_api"; document.head.appendChild(script); } } }- randykepple1 month agoCommunity Member
Nedim, you are brilliant and I'm here for it! :) I appreciate the time you have spent working to help me find a solution. I feel this is time well spent as this is such a common request and understanding how to do this repeatedly is a good skill to learn.
I did spent quite a bit of time last week testing various solutions and even did some vibe coding with AI to see if that would help me find success, but as of this morning, nothing seems to work. I did review Articulates documentation on Web Objects and was testing exactly as you suggested to force loading the new version and not the cached version. The AI agent was helping me troubleshoot and we supposedly narrowed it down to the script as the video was not being blocked from embedding and everything points to it working, but it just won't load.
I will continue testing later this afternoon with the scripts you have provided. Instructional Designers come into this profession from a lot of varied backgrounds and experiences. This is a prime example of the skill stacking that is the heart and soul of the work we do. Thank you for sharing your skills in assisting me with this frustrating situation.
Related Content
- 1 year ago
- 4 months ago