PageSourceSearch

https://teachmephysiology.com/wp-content/plugins/ai-tools/assets/js/audio-analytics.js?ver=1.2.3

js teachmephysiology.com collected 2026-10-01 11:43:32 UTC 7,391 bytes, 260 lines download raw bytes

1class AudioAnalytics {
2    constructor(postId, ajaxUrl, nonce) {
3        this.postId = postId;
4        this.ajaxUrl = ajaxUrl;
5        this.nonce = nonce;
6
7        const sessionIdKey = `audio_session_id_${postId}`;
8        let sessionId = localStorage.getItem(sessionIdKey);
9
10        if (!sessionId) {
11            sessionId = this.generateSessionId();
12            localStorage.setItem(sessionIdKey, sessionId);
13        }
14
15        this.sessionId = sessionId;
16        this.sessionIdKey = sessionIdKey;
17        this.storageKey = `audio_session_${postId}_${this.sessionId}`;
18
19        this.startTime = Date.now();
20        this.lastUpdate = Date.now();
21        this.listenTime = 0;
22        this.furthestPosition = 0;
23        this.playCount = 0;
24        this.pauseCount = 0;
25        this.seekCount = 0;
26        this.currentSpeed = 1.0;
27        this.completed = false;
28        this.milestones = new Set();
29
30        this.heartbeatInterval = null;
31        this.isPlaying = false;
32        this.lastSync = Date.now();
33
34        this.loadSession();
35
36        this.setupCleanupHandlers();
37    }
38
39    generateSessionId() {
40        return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/x/g, () =>
41            ((Math.random() * 16) | 0).toString(16)
42        );
43    }
44
45    loadSession() {
46        try {
47            const saved = localStorage.getItem(this.storageKey);
48            if (saved) {
49                const data = JSON.parse(saved);
50
51                if (data.completed) {
52                    localStorage.removeItem(this.storageKey);
53                    localStorage.removeItem(this.sessionIdKey);
54                    return;
55                }
56
57                this.listenTime = data.listenTime || 0;
58                this.furthestPosition = data.furthestPosition || 0;
59                this.playCount = data.playCount || 0;
60                this.pauseCount = data.pauseCount || 0;
61                this.seekCount = data.seekCount || 0;
62                this.currentSpeed = data.currentSpeed || 1.0;
63                this.completed = data.completed || false;
64                this.milestones = new Set(data.milestones || []);
65            }
66        } catch (e) {
67        }
68    }
69
70    saveSession() {
71        try {
72            localStorage.setItem(this.storageKey, JSON.stringify({
73                sessionId: this.sessionId,
74                listenTime: this.listenTime,
75                furthestPosition: this.furthestPosition,
76                playCount: this.playCount,
77                pauseCount: this.pauseCount,
78                seekCount: this.seekCount,
79                currentSpeed: this.currentSpeed,
80                completed: this.completed,
81                milestones: Array.from(this.milestones),
82                lastSync: Date.now()
83            }));
84        } catch (e) {
85        }
86    }
87
88    onPlay() {
89        if (!this.isPlaying) {
90            this.playCount++;
91            this.isPlaying = true;
92            this.lastUpdate = Date.now();
93
94            if (this.playCount === 1 && typeof gtag !== 'undefined') {
95                gtag('event', 'audio_play', {
96                    event_category: 'Audio Player',
97                    post_id: this.postId
98                });
99            }
100
101            this.startHeartbeat();
102        }
103    }
104
105    onPause() {
106        if (this.isPlaying) {
107            this.pauseCount++;
108            this.isPlaying = false;
109            this.updateListenTime();
110            this.stopHeartbeat();
111
112            this.syncToServer();
113        }
114    }
115
116    onEnded() {
117        this.isPlaying = false;
118        this.updateListenTime();
119        this.stopHeartbeat();
120        this.syncToServer();
121    }
122
123    onTimeUpdate(currentTime, duration) {
124        if (!this.isPlaying || !duration || duration <= 0) return;
125
126        this.updateListenTime();
127
128        const position = (currentTime / duration) * 100;
129        if (position > this.furthestPosition) {
130            this.furthestPosition = position;
131        }
132
133        this.checkMilestones(position);
134
135        if (position >= 95 && !this.completed) {
136            this.completed = true;
137            if (typeof gtag !== 'undefined') {
138                gtag('event', 'audio_complete', {
139                    event_category: 'Audio Player',
140                    post_id: this.postId,
141                    listen_duration: Math.round(this.listenTime)
142                });
143            }
144        }
145    }
146
147    updateListenTime() {
148        const now = Date.now();
149        const delta = (now - this.lastUpdate) / 1000;
150
151        if (delta > 0 && delta < 5) {
152            this.listenTime += delta;
153        }
154        this.lastUpdate = now;
155        this.saveSession();
156    }
157
158    checkMilestones(position) {
159        [25, 50, 75].forEach(milestone => {
160            if (position >= milestone && !this.milestones.has(milestone)) {
161                this.milestones.add(milestone);
162
163                if (typeof gtag !== 'undefined') {
164                    gtag('event', 'audio_progress', {
165                        event_category: 'Audio Player',
166                        post_id: this.postId,
167                        progress_percent: milestone
168                    });
169                }
170            }
171        });
172    }
173
174    onSeek() {
175        this.seekCount++;
176        this.saveSession();
177    }
178
179    onSpeedChange(speed) {
180        this.currentSpeed = speed;
181        this.saveSession();
182    }
183
184    startHeartbeat() {
185        this.heartbeatInterval = setInterval(() => {
186            if (this.isPlaying) {
187                this.syncToServer();
188            }
189        }, 30000);
190    }
191
192    stopHeartbeat() {
193        if (this.heartbeatInterval) {
194            clearInterval(this.heartbeatInterval);
195            this.heartbeatInterval = null;
196        }
197    }
198
199    syncToServer(forceSync = false) {
200        if (this.playCount === 0 || (this.listenTime === 0 && this.furthestPosition === 0)) {
201            return;
202        }
203
204        const now = Date.now();
205
206        if (!forceSync && now - this.lastSync < 5000) {
207            return;
208        }
209        this.lastSync = now;
210
211        const data = new URLSearchParams({
212            action: 'sync_audio_session',
213            nonce: this.nonce,
214            session_id: this.sessionId,
215            post_id: this.postId,
216            listen_time: Math.round(this.listenTime),
217            furthest_position: this.furthestPosition.toFixed(2),
218            completed: this.completed ? '1' : '0',
219            play_count: this.playCount,
220            pause_count: this.pauseCount,
221            seek_count: this.seekCount,
222            final_speed: this.currentSpeed
223        });
224
225        if (navigator.sendBeacon) {
226            navigator.sendBeacon(this.ajaxUrl, data);
227        } else {
228            jQuery.post(this.ajaxUrl, data);
229        }
230    }
231
232    setupCleanupHandlers() {
233        document.addEventListener('visibilitychange', () => {
234            if (document.hidden && this.isPlaying) {
235                this.updateListenTime();
236                this.syncToServer();
237            }
238        });
239
240        window.addEventListener('beforeunload', () => {
241            this.onBeforeUnload();
242        });
243    }
244
245    onBeforeUnload() {
246        if (this.isPlaying) {
247            this.updateListenTime();
248        }
249
250        this.syncToServer(true);
251    }
252
253    getResumePosition() {
254        if (!this.completed && this.furthestPosition > 5 && this.furthestPosition < 95) {
255            return this.furthestPosition;
256        }
257        return 0;
258    }
259}
260

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.