PageSourceSearch

https://vanstockum.blog/wp-content/plugins/reggie-audiobook-player-6/assets/js/player.js?ver=7.0.0

js vanstockum.blog collected 2026-10-04 06:36:40 UTC 10,235 bytes, 222 lines download raw bytes

1/**
2 * Reggie Audiobook Player — Frontend JS
3 * Bookmarking uses localStorage only — no login required.
4 */
5(function ($) {
6    'use strict';
7
8    /* ── Helpers ─────────────────────────────────────────────────────── */
9    function fmtTime(sec) {
10        sec = Math.floor(sec || 0);
11        var h = Math.floor(sec / 3600);
12        var m = Math.floor((sec % 3600) / 60);
13        var s = sec % 60;
14        if (h > 0) return h + ':' + pad(m) + ':' + pad(s);
15        return m + ':' + pad(s);
16    }
17    function pad(n) { return n < 10 ? '0' + n : n; }
18
19    /* ── Bookmark: localStorage only ────────────────────────────────── */
20    function bmKey(bookId) { return 'rap_bm_' + bookId; }
21
22    function bmSave(bookId, chapterIndex, timestamp) {
23        if (timestamp <= 0) return;
24        try {
25            localStorage.setItem(bmKey(bookId), JSON.stringify({
26                chapter_index: chapterIndex,
27                timestamp:     timestamp
28            }));
29        } catch(e) {}
30    }
31
32    function bmGet(bookId) {
33        try {
34            var raw = localStorage.getItem(bmKey(bookId));
35            return raw ? JSON.parse(raw) : null;
36        } catch(e) { return null; }
37    }
38
39    /* ── Player init ─────────────────────────────────────────────────── */
40    function initPlayer(wrap) {
41        var bookId = wrap.data('book-id');
42        var audio  = document.getElementById('rap-audio-' + bookId);
43        if (!audio) return;
44        var $audio = $(audio);
45
46        var $chapters  = wrap.find('.rap-chapter-item');
47        var $fill      = wrap.find('.rap-progress-fill');
48        var $thumb     = wrap.find('.rap-progress-thumb');
49        var $bar       = wrap.find('.rap-progress-bar');
50        var $curTime   = wrap.find('.rap-time-current');
51        var $totalTime = wrap.find('.rap-time-total');
52        var $playPause = wrap.find('.rap-play-pause');
53        var $iconPlay  = wrap.find('.rap-icon-play');
54        var $iconPause = wrap.find('.rap-icon-pause');
55        var $speed     = wrap.find('.rap-speed');
56        var $volume    = wrap.find('.rap-volume');
57        var $status    = wrap.find('.rap-bookmark-status');
58
59        var currentChapter = 0;
60        var isSeeking      = false;
61        var statusTimer    = null;
62        var saveTimer      = null;
63
64        function showStatus(msg, duration) {
65            $status.text(msg).show();
66            clearTimeout(statusTimer);
67            if (duration) statusTimer = setTimeout(function () { $status.fadeOut(); }, duration);
68        }
69
70        /* ── Load chapter ────────────────────────────────────────────── */
71        function loadChapter(index, startTime) {
72            var $item = $chapters.eq(index);
73            if (!$item.length) return;
74            var url = $item.data('url');
75            if (!url) return;
76
77            currentChapter = index;
78            $chapters.removeClass('rap-active rap-playing');
79            $item.addClass('rap-active');
80            audio.src = url;
81            audio.load();
82
83            if (startTime > 0) {
84                audio.addEventListener('loadedmetadata', function onMeta() {
85                    audio.currentTime = startTime;
86                    audio.removeEventListener('loadedmetadata', onMeta);
87                });
88            }
89        }
90
91        /* ── Play / Pause ────────────────────────────────────────────── */
92        function setPlayState(playing) {
93            if (playing) {
94                $iconPlay.hide(); $iconPause.show();
95                $chapters.eq(currentChapter).addClass('rap-playing');
96                $playPause.attr('title', 'Pause');
97            } else {
98                $iconPlay.show(); $iconPause.hide();
99                $chapters.removeClass('rap-playing');
100                $playPause.attr('title', 'Play');
101            }
102        }
103
104        $playPause.on('click', function () {
105            if (audio.paused) {
106                if (!audio.getAttribute('src')) loadChapter(currentChapter, 0);
107                audio.play().catch(function (e) { console.warn('RAP play error:', e); });
108            } else {
109                audio.pause();
110            }
111        });
112
113        $audio.on('play',  function () { setPlayState(true); });
114        $audio.on('pause', function () { setPlayState(false); });
115        $audio.on('ended', function () {
116            var next = currentChapter + 1;
117            if ($chapters.eq(next).length) { loadChapter(next, 0); audio.play(); }
118            else { setPlayState(false); }
119        });
120
121        /* ── Progress bar ────────────────────────────────────────────── */
122        $audio.on('timeupdate', function () {
123            if (isSeeking) return;
124            var pct = audio.duration ? (audio.currentTime / audio.duration) * 100 : 0;
125            $fill.css('width', pct + '%');
126            $thumb.css('left', pct + '%');
127            $curTime.text(fmtTime(audio.currentTime));
128        });
129
130        $audio.on('durationchange', function () {
131            $totalTime.text(fmtTime(audio.duration));
132        });
133
134        $bar.on('mousedown touchstart', function (e) { isSeeking = true; seek(e); });
135        $(document).on('mousemove touchmove', function (e) { if (isSeeking) seek(e); })
136                   .on('mouseup touchend',    function ()  { if (!isSeeking) return; isSeeking = false; scheduleSave(); });
137
138        function seek(e) {
139            var touch = e.originalEvent.touches ? e.originalEvent.touches[0] : e;
140            var rect  = $bar[0].getBoundingClientRect();
141            var ratio = Math.min(Math.max((touch.clientX - rect.left) / rect.width, 0), 1);
142            $fill.css('width', (ratio * 100) + '%');
143            $thumb.css('left', (ratio * 100) + '%');
144            if (audio.duration) audio.currentTime = ratio * audio.duration;
145            $curTime.text(fmtTime(audio.currentTime));
146        }
147
148        /* ── Skip buttons ────────────────────────────────────────────── */
149        wrap.find('.rap-back').on('click', function () { audio.currentTime = Math.max(0, audio.currentTime - 10); });
150        wrap.find('.rap-fwd').on('click',  function () { audio.currentTime = Math.min(audio.duration || 0, audio.currentTime + 30); });
151        wrap.find('.rap-prev').on('click', function () {
152            if (currentChapter > 0) { loadChapter(currentChapter - 1, 0); if (!audio.paused) audio.play(); }
153        });
154        wrap.find('.rap-next').on('click', function () {
155            if (currentChapter < $chapters.length - 1) { loadChapter(currentChapter + 1, 0); if (!audio.paused) audio.play(); }
156        });
157
158        /* ── Chapter list clicks ─────────────────────────────────────── */
159        $chapters.on('click', function () {
160            loadChapter(parseInt($(this).data('index'), 10), 0);
161            audio.play();
162        });
163
164        /* ── Speed & Volume ──────────────────────────────────────────── */
165        $speed.on('change', function () { audio.playbackRate = parseFloat($(this).val()); });
166        $volume.on('input', function () { audio.volume = parseFloat($(this).val()); });
167
168        /* ── Chapter duration hints ──────────────────────────────────── */
169        $chapters.each(function () {
170            var $item = $(this);
171            var url   = $item.data('url');
172            if (!url) return;
173            var tmp = new Audio();
174            tmp.preload = 'metadata';
175            tmp.src = url;
176            tmp.addEventListener('loadedmetadata', function () {
177                $item.find('.rap-chapter-duration').text(fmtTime(tmp.duration));
178            });
179        });
180
181        /* ── Save bookmark ───────────────────────────────────────────── */
182        function scheduleSave() {
183            clearTimeout(saveTimer);
184            saveTimer = setTimeout(function () {
185                bmSave(bookId, currentChapter, audio.currentTime);
186                showStatus('Progress saved \u2713', 2000);
187            }, 1500);
188        }
189
190        // Save on pause
191        $audio.on('pause', scheduleSave);
192
193        // Save every 15 s while playing
194        var autoSaveInterval = setInterval(function () {
195            if (!audio.paused && audio.currentTime > 0) {
196                bmSave(bookId, currentChapter, audio.currentTime);
197                showStatus('Progress saved \u2713', 2000);
198            }
199        }, 15000);
200
201        // Save synchronously when page closes (localStorage is sync so this always works)
202        $(window).on('beforeunload', function () {
203            clearInterval(autoSaveInterval);
204            bmSave(bookId, currentChapter, audio.currentTime);
205        });
206
207        /* ── Restore bookmark on load ────────────────────────────────── */
208        loadChapter(0, 0);
209
210        var bm = bmGet(bookId);
211        if (bm && (bm.chapter_index > 0 || bm.timestamp >= 5)) {
212            loadChapter(bm.chapter_index || 0, parseFloat(bm.timestamp) || 0);
213            showStatus('Resuming from where you left off\u2026');
214        }
215    }
216
217    /* ── Boot ────────────────────────────────────────────────────────── */
218    $(function () {
219        $('.rap-player').each(function () { initPlayer($(this)); });
220    });
221
222}(jQuery));

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.