';
streamQueue.forEach((item, index) => {
if (item) {
+ const isFirst = index === 0;
+ const isLast = index === streamQueue.length - 1;
html += `
-
+
${index + 1}
${item.title || 'Unknown'}
${item.artist || 'Unknown Artist'}
-
+
+
+
+
+
`;
}
@@ -584,3 +590,71 @@ async function updateLiveStreamInfo() {
}
}
}
+
+// Load queue from M3U file
+async function loadQueueFromM3U() {
+ if (!confirm('Load queue from stream-queue.m3u file? This will replace the current queue.')) {
+ return;
+ }
+
+ try {
+ const response = await fetch('/api/asteroid/stream/queue/load-m3u', {
+ method: 'POST'
+ });
+ const result = await response.json();
+ const data = result.data || result;
+
+ if (data.status === 'success') {
+ alert(`Successfully loaded ${data.count} tracks from M3U file!`);
+ loadStreamQueue();
+ } else {
+ alert('Error loading from M3U: ' + (data.message || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Error loading from M3U:', error);
+ alert('Error loading from M3U: ' + error.message);
+ }
+}
+
+// Move track up in queue
+async function moveTrackUp(index) {
+ if (index === 0) return;
+
+ // Swap with previous track
+ const newQueue = [...streamQueue];
+ [newQueue[index - 1], newQueue[index]] = [newQueue[index], newQueue[index - 1]];
+
+ await reorderQueue(newQueue);
+}
+
+// Move track down in queue
+async function moveTrackDown(index) {
+ if (index === streamQueue.length - 1) return;
+
+ // Swap with next track
+ const newQueue = [...streamQueue];
+ [newQueue[index], newQueue[index + 1]] = [newQueue[index + 1], newQueue[index]];
+
+ await reorderQueue(newQueue);
+}
+
+// Reorder the queue
+async function reorderQueue(newQueue) {
+ try {
+ const trackIds = newQueue.map(track => track.id).join(',');
+ const response = await fetch('/api/asteroid/stream/queue/reorder?track-ids=' + trackIds, {
+ method: 'POST'
+ });
+ const result = await response.json();
+ const data = result.data || result;
+
+ if (data.status === 'success') {
+ loadStreamQueue();
+ } else {
+ alert('Error reordering queue: ' + (data.message || 'Unknown error'));
+ }
+ } catch (error) {
+ console.error('Error reordering queue:', error);
+ alert('Error reordering queue: ' + error.message);
+ }
+}
diff --git a/stream-control.lisp b/stream-control.lisp
index 3bad8ee..afba1a0 100644
--- a/stream-control.lisp
+++ b/stream-control.lisp
@@ -96,6 +96,54 @@
(format stream "~a~%" docker-path))))))
t)
+(defun load-queue-from-m3u ()
+ "Load the stream queue from the existing M3U file"
+ (let ((playlist-path (merge-pathnames "stream-queue.m3u"
+ (asdf:system-source-directory :asteroid))))
+ (format t "Checking for M3U file at: ~a~%" playlist-path)
+ (if (probe-file playlist-path)
+ (handler-case
+ (progn
+ (format t "M3U file found, loading...~%")
+ (format t "Available collections: ~a~%" (db:collections))
+ (let ((all-tracks (db:select "tracks" (db:query :all))))
+ (format t "Found ~d tracks in database~%" (length all-tracks))
+ (when (> (length all-tracks) 0)
+ (format t "Sample track: ~a~%" (first all-tracks)))
+ (when (= (length all-tracks) 0)
+ (format t "โ Warning: No tracks in database. Please scan your music library first!~%")
+ (format t " Visit the Admin page and click 'Scan Library' to add tracks.~%")
+ (format t " After scanning, restart the server to load the queue.~%")
+ (return-from load-queue-from-m3u nil))
+ (with-open-file (stream playlist-path :direction :input)
+ (let ((track-ids '())
+ (line-count 0))
+ (loop for line = (read-line stream nil)
+ while line
+ do (progn
+ (incf line-count)
+ (when (and (> (length line) 0)
+ (not (char= (char line 0) #\#)))
+ ;; This is a file path line, find the track ID
+ (format t "Processing line ~d: ~a~%" line-count line)
+ (dolist (track all-tracks)
+ (let* ((file-path (gethash "file-path" track))
+ (file-path-str (if (listp file-path) (first file-path) file-path))
+ (docker-path (convert-to-docker-path file-path-str)))
+ (when (string= docker-path line)
+ (let ((id (gethash "_id" track)))
+ (push (if (listp id) (first id) id) track-ids)
+ (format t " Matched track ID: ~a~%" id))))))))
+ (setf *stream-queue* (nreverse track-ids))
+ (format t "โ Loaded ~d tracks from stream-queue.m3u~%" (length *stream-queue*))
+ (length *stream-queue*)))))
+ (error (e)
+ (format t "โ Error loading queue from M3U: ~a~%" e)
+ nil))
+ (progn
+ (format t "โ M3U file not found at: ~a~%" playlist-path)
+ nil))))
+
(defun regenerate-stream-playlist ()
"Regenerate the main stream playlist from the current queue"
(let ((playlist-path (merge-pathnames "stream-queue.m3u"
diff --git a/stream-queue.m3u b/stream-queue.m3u
index 4288342..c804f57 100644
--- a/stream-queue.m3u
+++ b/stream-queue.m3u
@@ -1,19 +1,37 @@
#EXTM3U
#EXTINF:0,
-/app/music/Model500/2015 - Digital Solutions/02-model_500-electric_night.flac
+/app/music/Vector Lovers/2005 - Capsule For One/08 - Empty Buildings, Falling Rain.mp3
#EXTINF:0,
-/app/music/Kraftwerk/1978 - The Man-Machine \[2009 Digital Remaster]/02 - Spacelab.flac
+/app/music/The Orb/1991 - The Orb's Adventures Beyond the Ultraworld (Double Album)/01 Little Fluffy Clouds.mp3
#EXTINF:0,
-/app/music/Kraftwerk/1981 - Computer World \[2009 Digital Remaster]/03 - Numbers.flac
+/app/music/Underworld/1996 - Second Toughest In The Infants/01. Underworld - Juanita, Kiteless, To Dream Of Love.flac
#EXTINF:0,
-/app/music/Model500/2015 - Digital Solutions/08-model_500-digital_solutions.flac
+/app/music/Vector Lovers/2005 - Capsule For One/02 - Arrival, Metropolis.mp3
#EXTINF:0,
-/app/music/Model500/2015 - Digital Solutions/02-model_500-electric_night.flac
+/app/music/Vector Lovers/2005 - Capsule For One/11 - Capsule For One.mp3
+#EXTINF:0,
+/app/music/Underworld/1996 - Second Toughest In The Infants/05. Underworld - Pearls Girl.flac
+#EXTINF:0,
+/app/music/Kraftwerk/1978 - The Man-Machine/04 - The Model.flac
+#EXTINF:0,
+/app/music/Model500/2015 - Digital Solutions/01-model_500-hi_nrg.flac
+#EXTINF:0,
+/app/music/Model500/2015 - Digital Solutions/07-model_500-station.flac
+#EXTINF:0,
+/app/music/Model500/\[1985] - Night Drive/01. Night Drive (Thru Babylon).mp3
+#EXTINF:0,
+/app/music/Model500/\[1988] - Interference/05. OK Corral.mp3
+#EXTINF:0,
+/app/music/This Mortal Coil/1984 - It'll End In Tears/02 - Song to the Siren.flac
#EXTINF:0,
/app/music/This Mortal Coil/1984 - It'll End In Tears/09 - Barramundi.flac
#EXTINF:0,
-/app/music/Underworld/1996 - Second Toughest In The Infants/04. Underworld - Rowla.flac
+/app/music/Boards of Canada/1998 - Music Has the Right to Children/07 Turquoise Hexagon Sun.flac
#EXTINF:0,
-/app/music/This Mortal Coil/1984 - It'll End In Tears/10 - Dreams Made Flesh.flac
+/app/music/Aphex Twin/1992 - Selected Ambient Works 85-92/09 Schottkey 7Th Path.flac
#EXTINF:0,
-/app/music/Underworld/1996 - Second Toughest In The Infants/07. Underworld - Blueski.flac
+/app/music/Aphex Twin/1992 - Selected Ambient Works 85-92/08 We Are the Music Makers.flac
+#EXTINF:0,
+/app/music/LaBradford/1995 - A Stable Reference/2 El Lago.flac
+#EXTINF:0,
+/app/music/Orbital/1993 - Orbital - Orbital 2 (Brown Album - TRUCD2, 828 386.2)/00. Planet Of The Shapes.mp3
diff --git a/template/admin.chtml b/template/admin.chtml
index 0397ba9..8d843c6 100644
--- a/template/admin.chtml
+++ b/template/admin.chtml
@@ -12,11 +12,11 @@
๐๏ธ ADMIN DASHBOARD
@@ -127,6 +127,7 @@
+
diff --git a/template/audio-player-frame.chtml b/template/audio-player-frame.chtml
new file mode 100644
index 0000000..1dfe76f
--- /dev/null
+++ b/template/audio-player-frame.chtml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
๐ข LIVE:
+
+
+
+
+
+
+
+
+
+
Loading...
+
+
+
+
+
diff --git a/template/frameset-wrapper.chtml b/template/frameset-wrapper.chtml
new file mode 100644
index 0000000..0cf1f2e
--- /dev/null
+++ b/template/frameset-wrapper.chtml
@@ -0,0 +1,23 @@
+
+
+
+
๐ต ASTEROID RADIO ๐ต
+
+
+
+
+
+
diff --git a/template/front-page-content.chtml b/template/front-page-content.chtml
new file mode 100644
index 0000000..f4e8c97
--- /dev/null
+++ b/template/front-page-content.chtml
@@ -0,0 +1,47 @@
+
+
+
+
๐ต ASTEROID RADIO ๐ต
+
+
+
+
+
+
+
+
+
+
+
+
+
Station Status
+
๐ข LIVE - Broadcasting asteroid music for hackers
+
Current listeners: 0
+
Stream quality: AAC 96kbps Stereo
+
+
+
+
๐ข LIVE STREAM
+
The live stream player is now in the persistent bar at the bottom of the page.
+
Stream URL:
+
Format:
+
Status: โ BROADCASTING
+
+
+
+
+
+
+
diff --git a/template/login.chtml b/template/login.chtml
index 7c315c2..94eafe3 100644
--- a/template/login.chtml
+++ b/template/login.chtml
@@ -11,10 +11,10 @@
@@ -37,11 +37,6 @@
-
- Default Admin Credentials:
- Username:
admin
- Password:
asteroid123
-