237 lines
8.6 KiB
Common Lisp
237 lines
8.6 KiB
Common Lisp
;; -*-lisp-*-
|
|
(defpackage :asteroid
|
|
(:use :cl)
|
|
(:use :asteroid.app-utils)
|
|
(:export :-main
|
|
:start-server
|
|
:stop-server
|
|
:run-server))
|
|
|
|
(in-package :asteroid)
|
|
|
|
;; Configuration
|
|
(defparameter *server-port* 8080)
|
|
(defparameter *server-host* "localhost")
|
|
(defparameter *acceptor* nil)
|
|
|
|
;; HTML generation helpers
|
|
(defun generate-page-html (title &rest body-content)
|
|
"Generate a complete HTML page with consistent styling"
|
|
(spinneret:with-html-string
|
|
(:doctype)
|
|
(:html
|
|
(:head
|
|
(:title title)
|
|
(:meta :charset "utf-8")
|
|
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
|
|
(:style "
|
|
body { font-family: 'Courier New', monospace; background: #0a0a0a; color: #00ff00; margin: 0; padding: 20px; }
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|
h1 { color: #ff6600; text-align: center; font-size: 2.5em; margin-bottom: 30px; }
|
|
h2 { color: #ff6600; }
|
|
.status { background: #1a1a1a; padding: 20px; border: 1px solid #333; margin: 20px 0; }
|
|
.panel { background: #1a1a1a; padding: 20px; border: 1px solid #333; margin: 20px 0; }
|
|
.nav { margin: 20px 0; }
|
|
.nav a { color: #00ff00; text-decoration: none; margin-right: 20px; padding: 10px; border: 1px solid #333; }
|
|
.nav a:hover { background: #333; }
|
|
.back { color: #00ff00; text-decoration: none; }
|
|
button { background: #333; color: #00ff00; border: 1px solid #555; padding: 10px 20px; margin: 5px; cursor: pointer; }
|
|
button:hover { background: #555; }
|
|
.player { background: #1a1a1a; padding: 40px; border: 1px solid #333; margin: 40px auto; max-width: 600px; text-align: center; }
|
|
.now-playing { font-size: 1.5em; margin: 20px 0; color: #ff6600; }
|
|
.controls button { padding: 15px 30px; margin: 10px; font-size: 1.2em; }
|
|
"))
|
|
(:body
|
|
(:div.container
|
|
(mapcar (lambda (element) element) body-content))))))
|
|
|
|
;; Route handlers
|
|
(defun handle-index ()
|
|
"Main page handler"
|
|
(spinneret:with-html-string
|
|
(:doctype)
|
|
(:html
|
|
(:head
|
|
(:title "Asteroid Radio - Music for Hackers")
|
|
(:meta :charset "utf-8")
|
|
(:meta :name "viewport" :content "width=device-width, initial-scale=1")
|
|
(:style "
|
|
body { font-family: 'Courier New', monospace; background: #0a0a0a; color: #00ff00; margin: 0; padding: 20px; }
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|
h1 { color: #ff6600; text-align: center; font-size: 2.5em; margin-bottom: 30px; }
|
|
h2 { color: #ff6600; }
|
|
.status { background: #1a1a1a; padding: 20px; border: 1px solid #333; margin: 20px 0; }
|
|
.nav { margin: 20px 0; }
|
|
.nav a { color: #00ff00; text-decoration: none; margin-right: 20px; padding: 10px; border: 1px solid #333; }
|
|
.nav a:hover { background: #333; }
|
|
"))
|
|
(:body
|
|
(:div.container
|
|
(:h1 "🎵 ASTEROID RADIO 🎵")
|
|
(:div.status
|
|
(:h2 "Station Status")
|
|
(:p "Status: " (:strong "Running"))
|
|
(:p "Now Playing: " (:em "Silence (for now)"))
|
|
(:p "Listeners: 0"))
|
|
(:div.nav
|
|
(:a :href "/admin" "Admin Dashboard")
|
|
(:a :href "/player" "Web Player")
|
|
(:a :href "/api/status" "API Status"))
|
|
(:div
|
|
(:h2 "Welcome to Asteroid Radio")
|
|
(:p "A streaming radio station for hackers, built with Common Lisp.")
|
|
(:p "Features coming soon:")
|
|
(:ul
|
|
(:li "Auto-DJ with crossfading")
|
|
(:li "Live DJ handoff")
|
|
(:li "Song requests")
|
|
(:li "Admin dashboard")
|
|
(:li "Music library management"))))))))
|
|
|
|
(defun handle-admin ()
|
|
"Admin dashboard handler"
|
|
(spinneret:with-html-string
|
|
(:doctype)
|
|
(:html
|
|
(:head
|
|
(:title "Asteroid Radio - Admin Dashboard")
|
|
(:meta :charset "utf-8")
|
|
(:style "
|
|
body { font-family: 'Courier New', monospace; background: #0a0a0a; color: #00ff00; margin: 0; padding: 20px; }
|
|
.container { max-width: 1200px; margin: 0 auto; }
|
|
h1 { color: #ff6600; }
|
|
.panel { background: #1a1a1a; padding: 20px; border: 1px solid #333; margin: 20px 0; }
|
|
button { background: #333; color: #00ff00; border: 1px solid #555; padding: 10px 20px; margin: 5px; cursor: pointer; }
|
|
button:hover { background: #555; }
|
|
.back { color: #00ff00; text-decoration: none; }
|
|
"))
|
|
(:body
|
|
(:div.container
|
|
(:a.back :href "/" "← Back to Main")
|
|
(:h1 "Admin Dashboard")
|
|
(:div.panel
|
|
(:h2 "Playback Control")
|
|
(:button "Play")
|
|
(:button "Pause")
|
|
(:button "Skip")
|
|
(:button "Stop"))
|
|
(:div.panel
|
|
(:h2 "Library Management")
|
|
(:button "Upload Music")
|
|
(:button "Manage Playlists")
|
|
(:button "Scan Library"))
|
|
(:div.panel
|
|
(:h2 "Live DJ")
|
|
(:button "Go Live")
|
|
(:button "End Session")
|
|
(:button "Mic Check"))
|
|
(:div.panel
|
|
(:h2 "System Status")
|
|
(:p "Server: Running")
|
|
(:p "Database: Not Connected")
|
|
(:p "Liquidsoap: Not Running")
|
|
(:p "Icecast: Not Running")))))))
|
|
|
|
(defun handle-player ()
|
|
"Web player handler"
|
|
(spinneret:with-html-string
|
|
(:doctype)
|
|
(:html
|
|
(:head
|
|
(:title "Asteroid Radio - Web Player")
|
|
(:meta :charset "utf-8")
|
|
(:style "
|
|
body { font-family: 'Courier New', monospace; background: #0a0a0a; color: #00ff00; margin: 0; padding: 20px; text-align: center; }
|
|
.player { background: #1a1a1a; padding: 40px; border: 1px solid #333; margin: 40px auto; max-width: 600px; }
|
|
.now-playing { font-size: 1.5em; margin: 20px 0; color: #ff6600; }
|
|
.controls button { background: #333; color: #00ff00; border: 1px solid #555; padding: 15px 30px; margin: 10px; font-size: 1.2em; cursor: pointer; }
|
|
.controls button:hover { background: #555; }
|
|
.back { color: #00ff00; text-decoration: none; }
|
|
"))
|
|
(:body
|
|
(:a.back :href "/" "← Back to Main")
|
|
(:div.player
|
|
(:h1 "🎵 ASTEROID RADIO PLAYER 🎵")
|
|
(:div.now-playing
|
|
(:div "Now Playing:")
|
|
(:div "Silence - The Sound of Startup"))
|
|
(:div.controls
|
|
(:button "▶ Play Stream")
|
|
(:button "⏸ Pause")
|
|
(:button "🔊 Volume"))
|
|
(:div
|
|
(:p "Stream URL: http://localhost:8000/asteroid")
|
|
(:p "Bitrate: 128kbps MP3")
|
|
(:p "Status: Offline")))))))
|
|
|
|
(defun handle-api-status ()
|
|
"API status endpoint handler"
|
|
(setf (hunchentoot:content-type*) "application/json")
|
|
(cl-json:encode-json-to-string
|
|
`(("status" . "running")
|
|
("server" . "asteroid-radio")
|
|
("version" . "0.1.0")
|
|
("uptime" . ,(get-universal-time))
|
|
("now-playing" . (("title" . "Silence")
|
|
("artist" . "The Void")
|
|
("album" . "Startup Sounds")))
|
|
("listeners" . 0)
|
|
("stream-url" . "http://localhost:8000/asteroid"))))
|
|
|
|
;; Route setup
|
|
(defun setup-routes ()
|
|
"Set up all HTTP routes"
|
|
(hunchentoot:define-easy-handler (index :uri "/") ()
|
|
(handle-index))
|
|
|
|
(hunchentoot:define-easy-handler (admin :uri "/admin") ()
|
|
(handle-admin))
|
|
|
|
(hunchentoot:define-easy-handler (player :uri "/player") ()
|
|
(handle-player))
|
|
|
|
(hunchentoot:define-easy-handler (api-status :uri "/api/status") ()
|
|
(handle-api-status)))
|
|
|
|
;; Server management functions
|
|
(defun start-server (&key (port *server-port*) (host *server-host*))
|
|
"Start the Asteroid Radio web server"
|
|
(when *acceptor*
|
|
(hunchentoot:stop *acceptor*))
|
|
|
|
(format t "Setting up routes...~%")
|
|
(setup-routes)
|
|
|
|
(format t "Starting Asteroid Radio server on ~a:~a~%" host port)
|
|
(setf *acceptor* (make-instance 'hunchentoot:easy-acceptor
|
|
:port port
|
|
:address host))
|
|
(hunchentoot:start *acceptor*)
|
|
(format t "Server started! Visit http://~a:~a~%" host port))
|
|
|
|
(defun stop-server ()
|
|
"Stop the Asteroid Radio web server"
|
|
(when *acceptor*
|
|
(format t "Stopping Asteroid Radio server...~%")
|
|
(hunchentoot:stop *acceptor*)
|
|
(setf *acceptor* nil)
|
|
(format t "Server stopped.~%")))
|
|
|
|
(defun run-server (&key (port *server-port*) (host *server-host*))
|
|
"Start the server and keep it running (blocking)"
|
|
(start-server :port port :host host)
|
|
(format t "Server running. Press Ctrl+C to stop.~%")
|
|
;; Keep the server running
|
|
(handler-case
|
|
(loop (sleep 1))
|
|
(sb-sys:interactive-interrupt ()
|
|
(format t "~%Received interrupt, stopping server...~%")
|
|
(stop-server))))
|
|
|
|
(defun -main (&optional args)
|
|
(declare (ignore args))
|
|
(format t "~%🎵 ASTEROID RADIO - Music for Hackers 🎵~%")
|
|
(format t "Starting web server...~%")
|
|
(run-server))
|
|
|