Edgenuity Video Speed Controller & Auto-Forward

Control video playback speed (2x, 3x, 5x, 10x) and auto-forward when videos complete

Size

12.5 KB

Version

1.1.1

Created

Dec 4, 2025

Updated

4 days ago

1// ==UserScript==
2// @name		Edgenuity Video Speed Controller & Auto-Forward
3// @description		Control video playback speed (2x, 3x, 5x, 10x) and auto-forward when videos complete
4// @version		1.1.1
5// @match		https://*.r03.core.learn.edgenuity.com/*
6// @icon		https://r03.core.learn.edgenuity.com/Player/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    console.log('Edgenuity Video Controller: Extension loaded');
12
13    let speedCheckInterval = null;
14    let autoForwardCheckInterval = null;
15
16    // Debounce function to prevent excessive calls
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            const later = () => {
21                clearTimeout(timeout);
22                func(...args);
23            };
24            clearTimeout(timeout);
25            timeout = setTimeout(later, wait);
26        };
27    }
28
29    // Create control panel
30    function createControlPanel() {
31        // Check if panel already exists
32        if (document.getElementById('edgenuity-control-panel')) {
33            console.log('Control panel already exists');
34            return;
35        }
36
37        const panel = document.createElement('div');
38        panel.id = 'edgenuity-control-panel';
39        panel.innerHTML = `
40            <div style="display: flex; align-items: center; gap: 15px;">
41                <div style="display: flex; align-items: center; gap: 8px;">
42                    <label style="color: white; font-weight: bold; font-size: 14px;">
43                        <input type="checkbox" id="auto-forward-toggle" style="margin-right: 5px; cursor: pointer;">
44                        Auto-Forward
45                    </label>
46                </div>
47                <div style="border-left: 2px solid rgba(255,255,255,0.3); height: 30px;"></div>
48                <div style="display: flex; align-items: center; gap: 8px;">
49                    <span style="color: white; font-weight: bold; font-size: 14px;">Speed:</span>
50                    <button class="speed-btn" data-speed="1">1x</button>
51                    <button class="speed-btn" data-speed="2">2x</button>
52                    <button class="speed-btn" data-speed="3">3x</button>
53                    <button class="speed-btn" data-speed="5">5x</button>
54                    <button class="speed-btn" data-speed="10">10x</button>
55                </div>
56            </div>
57        `;
58
59        // Add styles
60        const style = document.createElement('style');
61        style.textContent = `
62            #edgenuity-control-panel {
63                position: fixed;
64                top: 10px;
65                left: 50%;
66                transform: translateX(-50%);
67                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
68                padding: 12px 20px;
69                border-radius: 10px;
70                box-shadow: 0 4px 15px rgba(0,0,0,0.3);
71                z-index: 999999;
72                font-family: Arial, sans-serif;
73            }
74            
75            .speed-btn {
76                background: white;
77                border: 2px solid transparent;
78                padding: 6px 14px;
79                border-radius: 6px;
80                cursor: pointer;
81                font-weight: bold;
82                font-size: 13px;
83                color: #667eea;
84                transition: all 0.2s ease;
85            }
86            
87            .speed-btn:hover {
88                background: #f0f0f0;
89                transform: translateY(-2px);
90                box-shadow: 0 2px 8px rgba(0,0,0,0.2);
91            }
92            
93            .speed-btn.active {
94                background: #4CAF50;
95                color: white;
96                border-color: #45a049;
97            }
98            
99            #auto-forward-toggle {
100                width: 16px;
101                height: 16px;
102            }
103        `;
104        document.head.appendChild(style);
105        document.body.appendChild(panel);
106
107        console.log('Control panel created');
108        setupEventListeners();
109    }
110
111    // Setup event listeners for the control panel
112    async function setupEventListeners() {
113        // Load saved settings
114        const autoForwardEnabled = await GM.getValue('autoForwardEnabled', false);
115        const savedSpeed = await GM.getValue('videoSpeed', 1);
116
117        const autoForwardToggle = document.getElementById('auto-forward-toggle');
118        if (autoForwardToggle) {
119            autoForwardToggle.checked = autoForwardEnabled;
120        }
121
122        // Highlight active speed button
123        document.querySelectorAll('.speed-btn').forEach(btn => {
124            if (parseFloat(btn.dataset.speed) === savedSpeed) {
125                btn.classList.add('active');
126            }
127        });
128
129        // Auto-forward toggle
130        if (autoForwardToggle) {
131            autoForwardToggle.addEventListener('change', async (e) => {
132                const enabled = e.target.checked;
133                await GM.setValue('autoForwardEnabled', enabled);
134                console.log('Auto-forward:', enabled ? 'enabled' : 'disabled');
135                
136                if (enabled) {
137                    startAutoForwardMonitoring();
138                } else {
139                    stopAutoForwardMonitoring();
140                }
141            });
142        }
143
144        // Speed buttons
145        document.querySelectorAll('.speed-btn').forEach(btn => {
146            btn.addEventListener('click', async (e) => {
147                const speed = parseFloat(e.target.dataset.speed);
148                await GM.setValue('videoSpeed', speed);
149                
150                // Update active state
151                document.querySelectorAll('.speed-btn').forEach(b => b.classList.remove('active'));
152                e.target.classList.add('active');
153                
154                console.log('Video speed set to:', speed + 'x');
155            });
156        });
157
158        // Start continuous speed monitoring
159        startSpeedMonitoring();
160
161        // Start auto-forward if enabled
162        if (autoForwardEnabled) {
163            startAutoForwardMonitoring();
164        }
165    }
166
167    // Continuously monitor and apply speed to all videos
168    function startSpeedMonitoring() {
169        if (speedCheckInterval) {
170            clearInterval(speedCheckInterval);
171        }
172
173        speedCheckInterval = setInterval(async () => {
174            const savedSpeed = await GM.getValue('videoSpeed', 1);
175            
176            // Check main document videos
177            const videos = document.querySelectorAll('video');
178            videos.forEach(video => {
179                if (video.playbackRate !== savedSpeed) {
180                    video.playbackRate = savedSpeed;
181                    console.log('Applied speed', savedSpeed + 'x to video in main document');
182                }
183            });
184
185            // Check iframe videos
186            const iframe = document.getElementById('stageFrame');
187            if (iframe) {
188                try {
189                    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
190                    const iframeVideos = iframeDoc.querySelectorAll('video');
191                    iframeVideos.forEach(video => {
192                        if (video.playbackRate !== savedSpeed) {
193                            video.playbackRate = savedSpeed;
194                            console.log('Applied speed', savedSpeed + 'x to video in iframe');
195                        }
196                    });
197                } catch (e) {
198                    // Cross-origin iframe, can't access
199                }
200            }
201        }, 500); // Check every 500ms to keep speed consistent
202    }
203
204    // Monitor for video completion and auto-forward
205    function startAutoForwardMonitoring() {
206        console.log('Starting auto-forward monitoring');
207
208        if (autoForwardCheckInterval) {
209            clearInterval(autoForwardCheckInterval);
210        }
211
212        autoForwardCheckInterval = setInterval(async () => {
213            const autoForwardEnabled = await GM.getValue('autoForwardEnabled', false);
214            if (!autoForwardEnabled) {
215                stopAutoForwardMonitoring();
216                return;
217            }
218
219            // Check main document videos
220            const videos = document.querySelectorAll('video');
221            videos.forEach(video => {
222                checkVideoEnded(video);
223            });
224
225            // Check iframe videos
226            const iframe = document.getElementById('stageFrame');
227            if (iframe) {
228                try {
229                    const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
230                    const iframeVideos = iframeDoc.querySelectorAll('video');
231                    iframeVideos.forEach(video => {
232                        checkVideoEnded(video);
233                    });
234                } catch (e) {
235                    // Cross-origin iframe, can't access
236                }
237            }
238        }, 1000); // Check every second
239    }
240
241    function stopAutoForwardMonitoring() {
242        if (autoForwardCheckInterval) {
243            clearInterval(autoForwardCheckInterval);
244            autoForwardCheckInterval = null;
245            console.log('Stopped auto-forward monitoring');
246        }
247    }
248
249    // Check if video has ended and trigger forward
250    function checkVideoEnded(video) {
251        if (!video) return;
252        
253        // Check if video is ended or very close to the end (within 1 second)
254        if (video.ended || (video.duration > 0 && video.currentTime >= video.duration - 1)) {
255            if (!video.hasAttribute('data-forwarded')) {
256                video.setAttribute('data-forwarded', 'true');
257                console.log('Video ended, attempting to auto-forward');
258                
259                setTimeout(() => {
260                    clickNextButton();
261                    // Reset the forwarded flag after a delay
262                    setTimeout(() => {
263                        video.removeAttribute('data-forwarded');
264                    }, 5000);
265                }, 1000);
266            }
267        } else {
268            // Reset flag if video is playing
269            if (video.hasAttribute('data-forwarded') && video.currentTime < video.duration - 2) {
270                video.removeAttribute('data-forwarded');
271            }
272        }
273    }
274
275    // Click the next/continue button
276    function clickNextButton() {
277        // Look for next/continue button in main document
278        const mainButtons = Array.from(document.querySelectorAll('button, a')).filter(el => {
279            const text = el.textContent.toLowerCase();
280            const dataBindAttr = el.getAttribute('data-bind') || '';
281            return /next|continue|forward/i.test(text) || 
282                   /next|continue/i.test(dataBindAttr);
283        });
284
285        // Try iframe buttons
286        const iframe = document.getElementById('stageFrame');
287        let iframeButtons = [];
288        if (iframe) {
289            try {
290                const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
291                iframeButtons = Array.from(iframeDoc.querySelectorAll('button, a')).filter(el => {
292                    const text = el.textContent.toLowerCase();
293                    const dataBindAttr = el.getAttribute('data-bind') || '';
294                    return /next|continue|forward/i.test(text) || 
295                           /next|continue/i.test(dataBindAttr);
296                });
297            } catch (e) {
298                // Cross-origin iframe
299            }
300        }
301
302        const allButtons = [...mainButtons, ...iframeButtons];
303        const nextButton = allButtons.find(btn => btn && btn.offsetParent !== null);
304        
305        if (nextButton) {
306            console.log('Clicking next button:', nextButton.textContent.trim());
307            nextButton.click();
308        } else {
309            console.log('No next button found');
310        }
311    }
312
313    // Initialize when DOM is ready
314    function init() {
315        if (document.readyState === 'loading') {
316            document.addEventListener('DOMContentLoaded', createControlPanel);
317        } else {
318            createControlPanel();
319        }
320
321        // Monitor for DOM changes with debouncing
322        const debouncedSpeedCheck = debounce(async () => {
323            const savedSpeed = await GM.getValue('videoSpeed', 1);
324            const videos = document.querySelectorAll('video');
325            videos.forEach(video => {
326                if (video.playbackRate !== savedSpeed) {
327                    video.playbackRate = savedSpeed;
328                }
329            });
330        }, 300);
331
332        const observer = new MutationObserver(debouncedSpeedCheck);
333        observer.observe(document.body, {
334            childList: true,
335            subtree: true
336        });
337    }
338
339    init();
340})();
Edgenuity Video Speed Controller & Auto-Forward | Robomonkey