{"version":"1.0.1","templatekey":"multivideoplayerwidget","name":"Player - Multi Video Player","instructions":"<p>An HTML5 video player widget that will play the video file only a fixed number of times. Surround an html5 player or media link with this widget's tags.</p>","content":"<div id='{{AUTOID}}_container' class=\"my-3\">\n  <figure id='{{AUTOID}}' class='poodll_multivideoplayer d-none d-inline-block mw-100 mb-0 rounded overflow-hidden shadow-sm' data-fullscreen=\"false\">\n    <video id='{{AUTOID}}_player' class=\"nomediaplugin d-block mw-100 h-auto\" preload=\"metadata\"></video>\n    <div id='{{AUTOID}}_controlbar' class='d-flex flex-wrap align-items-center gap-2 bg-dark text-white px-2 py-1'>\n        <button type='button' id='{{AUTOID}}_playbutton' class='btn btn-dark px-2'\n                aria-label=\"{{#str}} play, filter_genericotwo {{/str}}\">\n            <i class=\"fa fa-play fa-lg\" aria-hidden=\"true\"></i>\n        </button>\n        <div class=\"lh-sm\">\n            <div id='{{AUTOID}}_time' class='small font-monospace'></div>\n            <div id='{{AUTOID}}_playtime' class=\"small text-white-50\"></div>\n        </div>\n        <div class=\"d-flex align-items-center gap-1 ms-auto\">\n            <button type=\"button\" id=\"{{AUTOID}}_vol-down\" class=\"btn btn-dark btn-sm px-1\"\n                    aria-label=\"{{#str}} volumedown, filter_genericotwo {{/str}}\">\n                <i class=\"fa fa-volume-down\" aria-hidden=\"true\"></i>\n            </button>\n            <div class=\"progress\">\n                <div id=\"{{AUTOID}}_indicator\" class=\"progress-bar\"></div>\n            </div>\n            <button type=\"button\" id=\"{{AUTOID}}_vol-up\" class=\"btn btn-dark btn-sm px-1\"\n                    aria-label=\"{{#str}} volumeup, filter_genericotwo {{/str}}\">\n                <i class=\"fa fa-volume-up\" aria-hidden=\"true\"></i>\n            </button>\n        </div>\n    </div><!-- end of controlbar -->\n  </figure>\n</div>\n<div id=\"{{AUTOID}}_original\" class=\"poodllplayerwidgetnoshow\" style=\"display: none\">\n  {{canplaycount}} \n  {{quiz_strict_mode}}\n  {{quiz_remember_play_position}} \n  {{URLPARAM:attempt}}\n  {{URLPARAM:cmid}}\n","templateend":"</div>","variabledefaults":"canplaycount=1,quiz_strict_mode=false,quiz_remember_play_position=false","jscontent":"require(['filter_genericotwo/mediaparser'], (parser) => {\n\n    const figure = document.getElementById('{{AUTOID}}');\n    // The template runs this code twice, so we use a data attribute as a flag to prevent 2x running.\n    if (figure.dataset.playtime !== undefined) {\n        return;\n    }\n\n    // Video url.\n    const details = parser.parse('{{AUTOID}}_original', 'video');\n    // Set playtime attribute.\n    figure.dataset.playtime = '{{canplaycount}}';\n\n    const player = document.getElementById('{{AUTOID}}_player');\n    player.src = details.mediaurl;\n\n    const playButton = document.getElementById('{{AUTOID}}_playbutton');\n    const icon = playButton.querySelector('.fa');\n    const timeDisplay = document.getElementById('{{AUTOID}}_time');\n    const playtimeDisplay = document.getElementById('{{AUTOID}}_playtime');\n    const indicator = document.getElementById('{{AUTOID}}_indicator');\n\n    let lastSeconds = 0;\n    let canPlayThrough = player.readyState === 4;\n    const positionUpdateInterval = 1; // Each X seconds save the play position.\n    const isStrictQuiz = '{{quiz_strict_mode}}' === 'true' && '{{URLPARAM:attempt}}' !== '';\n    const isRememberPos = isStrictQuiz && '{{quiz_remember_play_position}}' === 'true';\n\n    icon.classList.remove('fa-minus');\n\n    // Set base volume and show it on the meter.\n    player.volume = 0.2;\n    const updateVolumeIndicator = () => {\n        indicator.style.width = (player.volume * 100) + '%';\n    };\n    updateVolumeIndicator();\n\n    // These use localStorage, keyed per attempt, if in strict mode.\n    // Trim the cache killer or the key would be different on each page load.\n    const useUrl = details.mediaurl.replace(/&?cachekiller=[^&]+/, '').replace('?', '');\n    const storageKeyPlayCount = useUrl + '_pc_' + '{{URLPARAM:attempt}}' + '_' + '{{URLPARAM:cmid}}';\n    const storageKeyPosition = useUrl + '_pos_' + '{{URLPARAM:attempt}}' + '_' + '{{URLPARAM:cmid}}';\n\n    const setStoredValue = (key, value) => localStorage.setItem(key, value);\n    const getStoredValue = (key) => localStorage.getItem(key);\n\n    const disableSubsequentPlays = () => {\n        playtimeDisplay.textContent = '{{#str}}remainingplays, filter_genericotwo{{/str}}: 0';\n        icon.classList.add('fa-minus');\n        figure.dataset.disabled = 'true';\n        playButton.disabled = true;\n    };\n\n    const getLastPos = () => {\n        const lastPos = Number(getStoredValue(storageKeyPosition));\n        return Number.isFinite(lastPos) && lastPos > 0 ? lastPos : 0;\n    };\n\n    const secondsToTime = (seconds) => {\n        if (!Number.isFinite(seconds)) {\n            return '00:00:00';\n        }\n        const date = new Date(0);\n        date.setSeconds(seconds);\n        return date.toISOString().substring(11, 19);\n    };\n\n    const fetchSeconds = () => isNaN(player.currentTime) ? 0 : player.currentTime;\n\n    const fetchTimeDisplay = () => {\n        let currentTime = fetchSeconds();\n        // Tweak the display if this was a reload.\n        if (currentTime === 0 && isRememberPos) {\n            const lastPos = getLastPos();\n            if (lastPos > 0) {\n                currentTime = lastPos;\n            }\n        }\n        const duration = isNaN(player.duration) ? 0 : player.duration;\n        return secondsToTime(Math.floor(currentTime)) + ' / ' + secondsToTime(Math.floor(duration));\n    };\n\n    const playMedia = () => {\n        // If playing just continue.\n        if (!player.paused) {\n            return;\n        }\n        icon.classList.remove('fa-play');\n        icon.classList.add('fa-circle-o-notch', 'fa-spin');\n\n        // If we are remembering position, set the new position.\n        if (isRememberPos) {\n            const lastPos = getLastPos();\n            if (lastPos > 0) {\n                player.currentTime = lastPos;\n                // If we can not play through yet, wait till we can.\n                if (!canPlayThrough) {\n                    player.addEventListener('canplaythrough', () => {\n                        player.play();\n                    }, {once: true});\n                    player.load();\n                    return;\n                }\n            }\n        }\n        player.play();\n    };\n\n    if (isStrictQuiz) {\n        const prevPlayCount = getStoredValue(storageKeyPlayCount);\n        if (prevPlayCount !== null) {\n            figure.dataset.playtime = prevPlayCount;\n            if (prevPlayCount === '0') {\n                disableSubsequentPlays();\n            }\n        }\n    }\n\n    // Remaining plays text.\n    playtimeDisplay.textContent = '{{#str}}remainingplays, filter_genericotwo{{/str}}: ' + figure.dataset.playtime + 'x';\n\n    // Volume buttons.\n    document.getElementById('{{AUTOID}}_vol-up').addEventListener('click', () => {\n        if (player.volume + 0.1 > 1) {\n            return;\n        }\n        player.volume += 0.1;\n        updateVolumeIndicator();\n    });\n    document.getElementById('{{AUTOID}}_vol-down').addEventListener('click', () => {\n        if (player.volume - 0.1 < 0) {\n            return;\n        }\n        player.volume -= 0.1;\n        updateVolumeIndicator();\n    });\n\n    player.addEventListener('canplaythrough', () => {\n        canPlayThrough = true;\n    });\n\n    // The widget starts hidden (d-none) to avoid a layout jump; show it once\n    // the video has its dimensions, or now if metadata is already loaded.\n    const revealWidget = () => figure.classList.remove('d-none');\n    if (player.readyState >= 1) {\n        revealWidget();\n    } else {\n        player.addEventListener('loadedmetadata', revealWidget, {once: true});\n    }\n\n    player.addEventListener('ended', () => {\n        const currentPlayCount = Number(figure.dataset.playtime) - 1;\n        // If in strict mode persist the play count and reset the position.\n        if (isStrictQuiz) {\n            setStoredValue(storageKeyPlayCount, currentPlayCount);\n            setStoredValue(storageKeyPosition, 0);\n            lastSeconds = 0;\n        }\n        figure.dataset.playtime = currentPlayCount;\n\n        icon.classList.remove('fa-circle-o-notch', 'fa-spin', 'fa-play');\n        player.pause();\n\n        if (currentPlayCount > 0) {\n            playtimeDisplay.textContent = '{{#str}}remainingplays, filter_genericotwo{{/str}}: ' + currentPlayCount + 'x';\n            icon.classList.add('fa-play');\n        } else {\n            disableSubsequentPlays();\n        }\n    });\n\n    document.getElementById('{{AUTOID}}_playbutton').addEventListener('click', () => {\n        if (Number(figure.dataset.playtime) === 0) {\n            return;\n        }\n        playMedia();\n    });\n\n    player.addEventListener('durationchange', () => {\n        if (isRememberPos) {\n            const lastPos = getLastPos();\n            const currentPos = player.currentTime;\n            if (currentPos < lastPos && currentPos > 0) {\n                player.pause();\n                player.currentTime = lastPos;\n                player.play();\n            }\n        }\n        timeDisplay.textContent = fetchTimeDisplay();\n    });\n\n    player.addEventListener('timeupdate', () => {\n        if (isRememberPos) {\n            const currentSeconds = fetchSeconds();\n            if (currentSeconds - lastSeconds > positionUpdateInterval) {\n                setStoredValue(storageKeyPosition, currentSeconds);\n                lastSeconds = currentSeconds;\n            }\n        }\n        timeDisplay.textContent = fetchTimeDisplay();\n    });\n\n    // Show current time.\n    timeDisplay.textContent = fetchTimeDisplay();\n});\n","importcss":"","customcss":".poodll_multivideoplayer .progress {\n    width: 5rem;\n    height: 0.5rem;\n}","dataset":"","datasetvars":"","allowedcontexts":"","allowedcontextids":""}