coursewatcher 1.0.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +7 -7
- package/public/css/styles.css +521 -81
- package/public/js/player.js +182 -139
- package/{bin/coursewatcher.js → src/cli.js} +8 -6
- package/src/controllers/video-controller.js +2 -0
- package/src/server.js +75 -61
- package/src/services/video-service.js +32 -0
- package/views/pages/player.ejs +135 -92
package/views/pages/player.ejs
CHANGED
|
@@ -6,112 +6,155 @@
|
|
|
6
6
|
<%- include('../partials/header') %>
|
|
7
7
|
|
|
8
8
|
<main class="container player-container">
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<div class="
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
<div class="player-layout">
|
|
10
|
+
<!-- Main Player Section -->
|
|
11
|
+
<div class="player-main">
|
|
12
|
+
<!-- Video Player Section -->
|
|
13
|
+
<section class="player-section">
|
|
14
|
+
<h1 class="video-title">
|
|
15
|
+
<%= video.title %>
|
|
16
|
+
</h1>
|
|
17
|
+
<div class="video-wrapper">
|
|
18
|
+
<video id="videoPlayer" class="plyr" playsinline controls
|
|
19
|
+
data-video-id="<%= video.id %>" data-saved-position="<%= video.position %>"
|
|
20
|
+
data-next-video-url="<%= adjacent.next ? '/video/' + adjacent.next : '' %>">
|
|
21
|
+
<source src="/api/videos/<%= video.id %>/stream" type="video/mp4">
|
|
22
|
+
Your browser does not support the video tag.
|
|
23
|
+
</video>
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
<!-- Auto-play Countdown Overlay -->
|
|
26
|
+
<div id="autoplayOverlay" class="autoplay-overlay hidden">
|
|
27
|
+
<div class="autoplay-content">
|
|
28
|
+
<button id="skipToNext" class="autoplay-countdown-ring"
|
|
29
|
+
title="Click to play now">
|
|
30
|
+
<svg viewBox="0 0 100 100">
|
|
31
|
+
<circle cx="50" cy="50" r="45" class="countdown-bg" />
|
|
32
|
+
<circle cx="50" cy="50" r="45" class="countdown-progress"
|
|
33
|
+
id="countdownProgress" />
|
|
34
|
+
</svg>
|
|
35
|
+
<span id="countdownNumber" class="countdown-number">5</span>
|
|
36
|
+
</button>
|
|
37
|
+
<p class="autoplay-text">Up Next</p>
|
|
38
|
+
<p id="nextVideoTitle" class="autoplay-next-title"></p>
|
|
39
|
+
<button id="cancelAutoplay" class="btn autoplay-cancel">Cancel</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
28
43
|
|
|
29
|
-
|
|
44
|
+
<!-- Navigation -->
|
|
45
|
+
<div class="player-nav">
|
|
46
|
+
<% if (adjacent.prev) { %>
|
|
47
|
+
<a href="/video/<%= adjacent.prev %>" class="nav-button prev">
|
|
48
|
+
← Previous
|
|
49
|
+
</a>
|
|
50
|
+
<% } else { %>
|
|
51
|
+
<span class="nav-button disabled">← Previous</span>
|
|
52
|
+
<% } %>
|
|
30
53
|
|
|
31
|
-
|
|
32
|
-
<a href="/video/<%= adjacent.next %>" class="nav-button next">
|
|
33
|
-
Next →
|
|
34
|
-
</a>
|
|
35
|
-
<% } else { %>
|
|
36
|
-
<span class="nav-button disabled">Next →</span>
|
|
37
|
-
<% } %>
|
|
38
|
-
</div>
|
|
39
|
-
</section>
|
|
54
|
+
<a href="/" class="nav-button home">📚 Back to Course</a>
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
56
|
+
<% if (adjacent.next) { %>
|
|
57
|
+
<a href="/video/<%= adjacent.next %>" class="nav-button next">
|
|
58
|
+
Next →
|
|
59
|
+
</a>
|
|
60
|
+
<% } else { %>
|
|
61
|
+
<span class="nav-button disabled">Next →</span>
|
|
62
|
+
<% } %>
|
|
63
|
+
</div>
|
|
64
|
+
</section>
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<span class="status-label">Status:</span>
|
|
50
|
-
<div class="status-buttons">
|
|
51
|
-
<button class="status-btn <%= video.status === 'unwatched' ? 'active' : '' %>"
|
|
52
|
-
data-status="unwatched">
|
|
53
|
-
○ Unwatched
|
|
54
|
-
</button>
|
|
55
|
-
<button class="status-btn <%= video.status === 'in-progress' ? 'active' : '' %>"
|
|
56
|
-
data-status="in-progress">
|
|
57
|
-
▶ In Progress
|
|
58
|
-
</button>
|
|
59
|
-
<button class="status-btn <%= video.status === 'completed' ? 'active' : '' %>"
|
|
60
|
-
data-status="completed">
|
|
61
|
-
✓ Completed
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
66
|
+
<!-- Video Info Section -->
|
|
67
|
+
<section class="video-info-section">
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
<!-- Status Controls -->
|
|
70
|
+
<div class="status-controls">
|
|
71
|
+
<span class="status-label">Status:</span>
|
|
72
|
+
<div class="status-buttons">
|
|
73
|
+
<button class="status-btn <%= video.status === 'unwatched' ? 'active' : '' %>"
|
|
74
|
+
data-status="unwatched">
|
|
75
|
+
○ Unwatched
|
|
76
|
+
</button>
|
|
77
|
+
<button class="status-btn <%= video.status === 'in-progress' ? 'active' : '' %>"
|
|
78
|
+
data-status="in-progress">
|
|
79
|
+
▶ In Progress
|
|
80
|
+
</button>
|
|
81
|
+
<button class="status-btn <%= video.status === 'completed' ? 'active' : '' %>"
|
|
82
|
+
data-status="completed">
|
|
83
|
+
✓ Completed
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
<!-- Keyboard Shortcuts Help -->
|
|
89
|
+
<details class="shortcuts-help">
|
|
90
|
+
<summary>⌨️ Keyboard Shortcuts</summary>
|
|
91
|
+
<div class="shortcuts-grid">
|
|
92
|
+
<div class="shortcut"><kbd>Space</kbd> / <kbd>K</kbd> Play/Pause</div>
|
|
93
|
+
<div class="shortcut"><kbd>M</kbd> Mute/Unmute</div>
|
|
94
|
+
<div class="shortcut"><kbd>F</kbd> Fullscreen</div>
|
|
95
|
+
<div class="shortcut"><kbd>←</kbd> Back 10s</div>
|
|
96
|
+
<div class="shortcut"><kbd>→</kbd> Forward 10s</div>
|
|
97
|
+
<div class="shortcut"><kbd>↑</kbd> Volume Up</div>
|
|
98
|
+
<div class="shortcut"><kbd>↓</kbd> Volume Down</div>
|
|
99
|
+
<div class="shortcut"><kbd>C</kbd> Captions</div>
|
|
100
|
+
</div>
|
|
101
|
+
</details>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<!-- Notes Section -->
|
|
105
|
+
<section class="notes-section">
|
|
106
|
+
<h2 class="section-title">📝 Notes</h2>
|
|
107
|
+
<textarea id="notesEditor" class="notes-editor"
|
|
108
|
+
placeholder="Take notes for this video... (Markdown supported)"><%= notes.content || '' %></textarea>
|
|
109
|
+
<div class="notes-actions">
|
|
110
|
+
<button id="saveNotes" class="btn btn-primary">Save Notes</button>
|
|
111
|
+
<span id="notesSaveStatus" class="save-status"></span>
|
|
112
|
+
</div>
|
|
113
|
+
</section>
|
|
81
114
|
</div>
|
|
82
115
|
|
|
83
|
-
<!--
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<div class="shortcut"><kbd>J</kbd> Back 5s</div>
|
|
91
|
-
<div class="shortcut"><kbd>K</kbd> Forward 5s</div>
|
|
92
|
-
<div class="shortcut"><kbd>L</kbd> Forward 10s</div>
|
|
93
|
-
<div class="shortcut"><kbd>Shift+J</kbd> Back 30s</div>
|
|
94
|
-
<div class="shortcut"><kbd>Shift+L</kbd> Forward 30s</div>
|
|
95
|
-
<div class="shortcut"><kbd>←</kbd> Back 5s</div>
|
|
96
|
-
<div class="shortcut"><kbd>→</kbd> Forward 5s</div>
|
|
116
|
+
<!-- Queue Panel -->
|
|
117
|
+
<aside class="queue-panel">
|
|
118
|
+
<div class="queue-header">
|
|
119
|
+
<h2 class="queue-title">📋 Queue</h2>
|
|
120
|
+
<span class="queue-module-name">
|
|
121
|
+
<%= queue.moduleName %>
|
|
122
|
+
</span>
|
|
97
123
|
</div>
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
124
|
+
<div class="queue-list">
|
|
125
|
+
<% queue.videos.forEach((qVideo, index)=> { %>
|
|
126
|
+
<a href="/video/<%= qVideo.id %>"
|
|
127
|
+
class="queue-item <%= qVideo.id === queue.currentId ? 'active' : '' %>"
|
|
128
|
+
data-video-id="<%= qVideo.id %>" data-video-title="<%= qVideo.title %>">
|
|
129
|
+
<span class="queue-item-number">
|
|
130
|
+
<%= index + 1 %>
|
|
131
|
+
</span>
|
|
132
|
+
<div class="queue-item-content">
|
|
133
|
+
<span class="queue-item-title">
|
|
134
|
+
<%= qVideo.title %>
|
|
135
|
+
</span>
|
|
136
|
+
<div class="queue-item-meta">
|
|
137
|
+
<span class="queue-item-status status-<%= qVideo.status %>">
|
|
138
|
+
<% if (qVideo.status==='completed' ) { %>✓
|
|
139
|
+
<% } else if (qVideo.status==='in-progress' ) { %>▶
|
|
140
|
+
<% } else { %>○<% } %>
|
|
141
|
+
</span>
|
|
142
|
+
<% if (qVideo.id===queue.currentId) { %>
|
|
143
|
+
<span class="queue-item-playing">Now Playing</span>
|
|
144
|
+
<% } %>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</a>
|
|
148
|
+
<% }); %>
|
|
149
|
+
</div>
|
|
150
|
+
</aside>
|
|
151
|
+
</div>
|
|
111
152
|
</main>
|
|
112
153
|
|
|
113
154
|
<%- include('../partials/footer') %>
|
|
114
155
|
|
|
156
|
+
<link rel="stylesheet" href="/lib/plyr/plyr.css" />
|
|
157
|
+
<script src="/lib/plyr/plyr.js"></script>
|
|
115
158
|
<script src="/static/js/player.js"></script>
|
|
116
159
|
</body>
|
|
117
160
|
|