PageSourceSearch

https://puzat.ru/wp-content/themes/puzatru/js/front-player.js?ver=1790863284

js puzat.ru collected 2026-10-04 00:19:29 UTC 9,424 bytes, 170 lines download raw bytes

1/**
2 * Свой плеер ролика на главной (блок «Программные продукты», вариант A1 «Кинокарточка» — выбор Романа 01.10.2026).
3 * Ролик встроен в страницу, файл грузится только по нажатию. Управление: пауза, время, перемотка (мышь, палец, клавиши),
4 * скорость, звук, полный экран; панель прячется через 2,5 с без движения. Клавиши в фокусе плеера: пробел/K — пауза,
5 * ← → — 5 с, F — полный экран, M — звук, Home/End. Разметка — front-page.php, стили — css/style-main.css (.front-player).
6 */
7(function () {
8    'use strict';
9
10    function fmt(s) {
11        s = Math.max(0, Math.floor(s || 0));
12        return Math.floor(s / 60) + ':' + ('0' + (s % 60)).slice(-2);
13    }
14
15    function build(root) {
16        var video = root.querySelector('video');
17        var known = parseFloat(root.getAttribute('data-duration')) || 0;
18        var bar = document.createElement('div');
19        bar.className = 'fp-bar';
20        bar.innerHTML =
21            '<button type="button" class="fp-btn fp-play" aria-label="Воспроизвести"><span class="fp-ic-play"></span></button>' +
22            '<span class="fp-time">0:00 / ' + fmt(known) + '</span>' +
23            '<div class="fp-seek" role="slider" tabindex="0" aria-label="Перемотка" aria-valuemin="0" aria-valuemax="' + Math.round(known) + '" aria-valuenow="0">' +
24            '<div class="fp-track"><div class="fp-buf"></div><div class="fp-fill"></div></div><div class="fp-knob"></div><div class="fp-tip"></div></div>' +
25            '<button type="button" class="fp-speed" aria-label="Скорость">1×</button>' +
26            '<button type="button" class="fp-btn fp-mute" aria-label="Выключить звук"><span class="fp-ic-vol"><i></i></span></button>' +
27            '<button type="button" class="fp-btn fp-fs" aria-label="Полный экран"><span class="fp-ic-fs"></span></button>';
28        root.appendChild(bar);
29        var spin = document.createElement('div');
30        spin.className = 'fp-spin';
31        root.appendChild(spin);
32
33        var playBtn = bar.querySelector('.fp-play'), time = bar.querySelector('.fp-time'), seek = bar.querySelector('.fp-seek'),
34            fill = bar.querySelector('.fp-fill'), buf = bar.querySelector('.fp-buf'), knob = bar.querySelector('.fp-knob'),
35            tip = bar.querySelector('.fp-tip'), speed = bar.querySelector('.fp-speed'), mute = bar.querySelector('.fp-mute'),
36            fs = bar.querySelector('.fp-fs');
37
38        function dur() { return isFinite(video.duration) && video.duration > 0 ? video.duration : known; }
39        function start() {
40            root.classList.add('is-started');
41            root.classList.remove('is-ended');
42            if (video.preload === 'none') { video.preload = 'auto'; }
43        }
44        function toggle() {
45            if (video.paused || video.ended) { start(); video.play(); } else { video.pause(); }
46        }
47        function render() {
48            var d = dur(), p = d ? video.currentTime / d : 0;
49            fill.style.width = (p * 100) + '%';
50            knob.style.left = (p * 100) + '%';
51            time.textContent = fmt(video.currentTime) + ' / ' + fmt(d);
52            seek.setAttribute('aria-valuemax', Math.round(d));
53            seek.setAttribute('aria-valuenow', Math.round(video.currentTime));
54            seek.setAttribute('aria-valuetext', fmt(video.currentTime) + ' из ' + fmt(d));
55        }
56
57        root.querySelector('.fp-start').addEventListener('click', function (e) {
58            e.stopPropagation();
59            if (root.classList.contains('was-ended')) { video.currentTime = 0; root.classList.remove('was-ended'); }
60            start();
61            video.play();
62        });
63        root.querySelector('.fp-cover').addEventListener('click', toggle);
64        video.addEventListener('click', toggle);
65        playBtn.addEventListener('click', toggle);
66
67        video.addEventListener('play', function () {
68            root.classList.add('is-playing');
69            playBtn.innerHTML = '<span class="fp-ic-pause"></span>';
70            playBtn.setAttribute('aria-label', 'Пауза');
71            poke();
72        });
73        video.addEventListener('pause', function () {
74            root.classList.remove('is-playing', 'is-idle');
75            playBtn.innerHTML = '<span class="fp-ic-play"></span>';
76            playBtn.setAttribute('aria-label', 'Воспроизвести');
77        });
78        video.addEventListener('ended', function () {
79            root.classList.add('is-ended', 'was-ended');
80            root.classList.remove('is-started');
81        });
82        video.addEventListener('waiting', function () { root.classList.add('is-waiting'); });
83        ['playing', 'canplay', 'seeked'].forEach(function (ev) {
84            video.addEventListener(ev, function () { root.classList.remove('is-waiting'); });
85        });
86        video.addEventListener('timeupdate', render);
87        video.addEventListener('loadedmetadata', render);
88        video.addEventListener('progress', function () {
89            if (video.buffered.length && dur()) { buf.style.width = (video.buffered.end(video.buffered.length - 1) / dur() * 100) + '%'; }
90        });
91        video.addEventListener('volumechange', function () {
92            root.classList.toggle('is-muted', video.muted);
93            mute.setAttribute('aria-label', video.muted ? 'Включить звук' : 'Выключить звук');
94        });
95
96        // перемотка мышью и пальцем
97        function pos(e) {
98            var r = seek.getBoundingClientRect();
99            return Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
100        }
101        seek.addEventListener('pointerdown', function (e) {
102            e.preventDefault();
103            seek.setPointerCapture(e.pointerId);
104            seek.classList.add('is-drag');
105            var wasPlaying = !video.paused;
106            video.pause();
107            start();
108            var move = function (ev) { video.currentTime = pos(ev) * dur(); render(); };
109            var up = function () {
110                seek.classList.remove('is-drag');
111                seek.removeEventListener('pointermove', move);
112                seek.removeEventListener('pointerup', up);
113                seek.removeEventListener('pointercancel', up);
114                if (wasPlaying) { video.play(); }
115            };
116            move(e);
117            seek.addEventListener('pointermove', move);
118            seek.addEventListener('pointerup', up);
119            seek.addEventListener('pointercancel', up);
120        });
121        seek.addEventListener('pointermove', function (e) {
122            var p = pos(e);
123            tip.style.left = (p * 100) + '%';
124            tip.textContent = fmt(p * dur());
125        });
126
127        speed.addEventListener('click', function () {
128            var rates = [1, 1.25, 1.5, 2], next = rates[(rates.indexOf(video.playbackRate) + 1) % rates.length];
129            video.playbackRate = next;
130            speed.textContent = String(next).replace('.', ',') + '×';
131        });
132        mute.addEventListener('click', function () { video.muted = !video.muted; });
133        fs.addEventListener('click', function () {
134            if (document.fullscreenElement) { document.exitFullscreen(); }
135            else if (root.requestFullscreen) { root.requestFullscreen(); }
136            else if (video.webkitEnterFullscreen) { video.webkitEnterFullscreen(); }  // iPhone: только сам <video>
137        });
138
139        // клавиши, когда плеер в фокусе
140        root.addEventListener('keydown', function (e) {
141            var k = e.key, d = dur();
142            if (k === ' ' || k === 'k' || k === 'л') { e.preventDefault(); toggle(); }
143            else if (k === 'ArrowRight') { e.preventDefault(); start(); video.currentTime = Math.min(d, video.currentTime + 5); }
144            else if (k === 'ArrowLeft') { e.preventDefault(); start(); video.currentTime = Math.max(0, video.currentTime - 5); }
145            else if (k === 'f' || k === 'а') { e.preventDefault(); fs.click(); }
146            else if (k === 'm' || k === 'ь') { e.preventDefault(); video.muted = !video.muted; }
147            else if (k === 'Home') { e.preventDefault(); video.currentTime = 0; }
148            else if (k === 'End') { e.preventDefault(); video.currentTime = Math.max(0, d - 1); }
149            poke();
150        });
151
152        // панель прячется через 2,5 с без движения во время просмотра
153        var idle = null;
154        function poke() {
155            root.classList.remove('is-idle');
156            clearTimeout(idle);
157            idle = setTimeout(function () {
158                if (!video.paused && !bar.contains(document.activeElement)) { root.classList.add('is-idle'); }
159            }, 2500);
160        }
161        root.addEventListener('mousemove', poke);
162        root.addEventListener('touchstart', poke, { passive: true });
163        root.addEventListener('focusin', poke);
164        root.addEventListener('mouseleave', function () { if (!video.paused) { root.classList.add('is-idle'); } });
165
166        render();
167    }
168
169    document.querySelectorAll('.front-player').forEach(build);
170})();

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.