<!DOCTYPE html>
<!-----------------------------------------------------------------------------
     Copyright 2014-2025 Jesper Larsson

     This file is part of Klipspringer, <https://klipspringer.avadeaux.net/>

     Klipspringer is free software: you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the Free
     Software Foundation, either version 3 of the License, or (at your option)
     any later version.

     Klipspringer is distributed in the hope that it will be useful, but
     WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
     or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
     for more details.

     You should have received a copy of the GNU General Public License along
     with Klipspringer. If not, see <https://www.gnu.org/licenses/>.
   --------------------------------------------------------------------------->

<html>
    <head>
        <meta charset="UTF-8">
        <style>$(klipstyle.css:file)</style>
        <title>Play</title>
        <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1">
        $(klipicons.html:file)
    </head>

    <body>
        $(controls_common.html:file)

        <script>
         'use strict';

         $(sendcmd.js:file)
         const source = $(source);              // the source element from sources.json

         let updateTimeTimer;                   // timer for updating displayed time as time goes by
         let trackTimeWhen;

         // If parameter is undefined, the time displayed is stopped and
         // nothing more happens. Otherwise, unless paused or page is not
         // visible, a loop is started to keep updating displayed time as time
         // goes by.
         const setTime = (trackTime) => {
             trackTimeWhen = Date.now();

             const updateTime = () => {
                 if (playStatus.paused) {
                     showTime(trackTime);
                 } else {
                     showTime(trackTime + (Date.now() - trackTimeWhen)/1000);
                     if (document.visibilityState === 'visible') { updateTimeTimer = setTimeout(updateTime, 500) }
                 }
             }

             clearTimeout(updateTimeTimer);             // there can be only one
             if (trackTime === undefined) { return }    // blink reduction
             showTime(trackTime);
             if (trackTime !== undefined && !playStatus.paused) {
                 updateTimeTimer = setTimeout(updateTime, (1 - trackTime%1) * 1000);
             }
         }

         let retryTimer;                // timer for retrying status check after it failed
         let retryTime = 200;           // time (milliseconds) until next retry, increases on multiple failures

         // Blanks all info fields.
         const clearPage = () => {
             clearTimeout(updateTimeTimer);
             blankInfo();
             playStatus.status_timestamp = 0;
         }

         let statusRequest = null;

         const statusError = (err) => {
             console.error("Failed to get info, "+err);
             clearPage();
             if (retryTime >= 10000) {
                 window.location.href = "/"; // give up and go home
             } else {
                 retryTimer = setTimeout(updateStatusLoop, retryTime); // loop
                 retryTime = Math.min(2*retryTime, 10000); // but slow down
             }
         }

         // Called when:
         // - the page is first loaded,
         // - a status request is satisfied,
         // - the page becomes visible, and
         // - it is time to retry, after a previous failure.
         // Cancels any pending status request or retry.
         const updateStatusLoop = () => {
             clearTimeout(retryTimer);  // there can be only one
             if (document.visibilityState !== 'visible') { return }   // we will get back here when page becomes visible
             if (statusRequest) {
                 statusRequest.cancelled = true;
                 statusRequest.controller.abort()
             }
             const req = statusRequest = { controller: new AbortController() }
             sendCmd('get_status', '&info_id='+playerInfoId+'&seen='+(playStatus.status_timestamp || "0"), { signal: req.controller.signal }).then(ans => {
                 if (ans.err) {
                     statusError(ans.err);
                 } else {
                     playStatus = ans;
                     updatePlayerInfo(playStatus);
                     updateTrackInfo(ans);
                     setTime(ans.track_time);
                     retryTimer = setTimeout(updateStatusLoop, retryTime = 200); // loop, with moderation
                 }
             }).catch(err => {
                 if (req.cancelled) { return }
                 statusError(err);
             });
         }

         const play = () => { sendCmd('play') }

         const setMonomix = (on) => { sendCmd('seekr', '&time=0&monomix='+(on ? "on" : "off")) }

         defineButtons({
             prev: () => { sendCmd('prev') },
             next: () => { sendCmd('next') },
             stop: () => { sendCmd('quit'); clearPage() },
             pause: () => { sendCmd('pause') },
             play: () => { play() },
             xrew: () => { sendCmd('seekr', '&time=-30') },
             rew: () => { sendCmd('seekr', '&time=-5') },
             ffd: () => { sendCmd('seekr', '&time=5') },
             xffd: () => { sendCmd('seekr', '&time=30') },
             home: () => { window.location.href = "/" },
             phones: source.phones_mirror === "" || isNaN(source.phones_mirror)
                   ? null
                   : () => {
                       const href = "/choice?source="+source.phones_mirror
                                   +"&path="+encodeURIComponent(sourcePath)
                                   +"&track_number="+playStatus.track_number
                                   +"&track_time="+(playStatus.track_time + (Date.now() - trackTimeWhen)/1000);
                       sendCmd('quit').then(() => { window.location.href = href });
                   }
         });
         const jumpTrack = (i) => { sendCmd('seeka', '&track='+i) }

         updateStatusLoop();
         document.addEventListener('visibilitychange', () => {
             if (document.visibilityState === 'visible') {
                 playStatus.status_timestamp = 0;
                 updateStatusLoop();
             } else {
                 if (statusRequest) {
                     statusRequest.cancelled = true;
                     statusRequest.controller.abort();
                 }
                 statusRequest = null;
             }
         });
        </script>
    </body>
</html>

Version: v4.3.2.2 (2026-05-16T17:03:34+02:00)
Raw file
Source code overview
Klipspringer home