{"version":"1.0.1","templatekey":"pw-multiplayeraudio","name":"Player - MultiPlayerAudio","instructions":"<p>An HTML5 audio player that will only play the audio a preconfigured number of times. Insert an audio player or audio link between the tags. If in a quiz, and strict mode is on, it can remember the play position and play count between page reloads.</p>","content":"<div id='{{AUTOID}}_container' class=\"poodll_multiplayer my-3\">\n  <audio id='{{AUTOID}}_player' class=\"nomediaplugin\" preload=\"auto\"></audio>\n  <div class='d-flex flex-wrap align-items-center gap-2 bg-dark text-white px-2 py-1 rounded shadow-sm'>\n    <button type='button' id='{{AUTOID}}' 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>\n</div>\n<div id=\"{{AUTOID}}_original\" class=\"poodllplayerwidgetnoshow\" style=\"display: none\">\n","templateend":"</div>","variabledefaults":"canplaycount=2,quiz_strict_mode=true|false,quiz_remember_play_position=true|false,volume=0.4|0.5|0.6|0.7|0.8|0.9","jscontent":"require(['filter_genericotwo/mediaparser'], (parser) => {\n\n    const playButton = 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 (playButton.dataset.playtime !== undefined) {\n        return;\n    }\n    playButton.dataset.playtime = '{{canplaycount}}';\n\n    const details = parser.parse('{{AUTOID}}_original', 'audio');\n    const player = document.getElementById('{{AUTOID}}_player');\n    player.src = details.mediaurl;\n\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    // 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    const storageKeyVolume = 'audiovol_' + '{{URLPARAM:attempt}}' + '_' + '{{URLPARAM:cmid}}';\n\n    const setStoredValue = (key, value) => localStorage.setItem(key, value);\n    const getStoredValue = (key) => localStorage.getItem(key);\n\n    const setPlayerVolume = (newVolume) => {\n        player.volume = newVolume;\n        indicator.style.width = (player.volume * 100) + '%';\n    };\n\n    const disableSubsequentPlays = () => {\n        playtimeDisplay.textContent = '{{#str}} remainingplays, filter_genericotwo {{/str}}: 0';\n        icon.classList.add('fa-minus');\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    // Set the base volume: the {{volume}} variable if valid, otherwise 0.4.\n    const baseVolume = Number('{{volume}}');\n    if ('{{volume}}' !== '' && Number.isFinite(baseVolume) && baseVolume >= 0 && baseVolume <= 1) {\n        setPlayerVolume(baseVolume);\n    } else {\n        setPlayerVolume(0.4);\n    }\n\n    if (isStrictQuiz) {\n        const prevPlayCount = getStoredValue(storageKeyPlayCount);\n        if (prevPlayCount !== null) {\n            playButton.dataset.playtime = prevPlayCount;\n            if (prevPlayCount === '0') {\n                disableSubsequentPlays();\n            }\n        }\n        const prevVolume = Number(getStoredValue(storageKeyVolume));\n        if (Number.isFinite(prevVolume) && prevVolume > 0 && prevVolume <= 1) {\n            setPlayerVolume(prevVolume);\n        }\n    }\n\n    // Remaining plays text.\n    playtimeDisplay.textContent = '{{#str}} remainingplays, filter_genericotwo {{/str}}: ' + playButton.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        setPlayerVolume(player.volume + 0.1);\n        setStoredValue(storageKeyVolume, player.volume);\n    });\n    document.getElementById('{{AUTOID}}_vol-down').addEventListener('click', () => {\n        if (player.volume - 0.1 < 0) {\n            return;\n        }\n        setPlayerVolume(player.volume - 0.1);\n        setStoredValue(storageKeyVolume, player.volume);\n    });\n\n    player.addEventListener('canplaythrough', () => {\n        canPlayThrough = true;\n    });\n\n    player.addEventListener('ended', () => {\n        const currentPlayCount = Number(playButton.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        playButton.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    playButton.addEventListener('click', () => {\n        if (Number(playButton.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_multiplayer {\n    max-width: 400px;\n}\n.poodll_multiplayer .progress {\n    width: 5rem;\n    height: 0.5rem;\n}","dataset":"","datasetvars":"","allowedcontexts":"","allowedcontextids":""}