Compare commits
No commits in common. "master" and "v24.25.0" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,4 +3,4 @@ bin/*
|
|||||||
.vscode
|
.vscode
|
||||||
*.log
|
*.log
|
||||||
*_templ.go
|
*_templ.go
|
||||||
/test
|
*.db3*
|
7
Makefile
7
Makefile
@ -6,7 +6,7 @@ SYSDDIR := ${SYSDDIR_:/%=%}
|
|||||||
DESTDIR ?=
|
DESTDIR ?=
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
VERSION ?= 24.38.0
|
VERSION ?= 24.25.0
|
||||||
|
|
||||||
GOFLAGS := -buildmode=pie -modcacherw -mod=readonly -trimpath
|
GOFLAGS := -buildmode=pie -modcacherw -mod=readonly -trimpath
|
||||||
LDFLAGS := -ldflags "-linkmode=external -extldflags \"${LDFLAGS}\" -s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
LDFLAGS := -ldflags "-linkmode=external -extldflags \"${LDFLAGS}\" -s -w -X main.version=${VERSION}" -tags osusergo,netgo
|
||||||
@ -24,8 +24,9 @@ endif
|
|||||||
|
|
||||||
run: | ${TARGET}
|
run: | ${TARGET}
|
||||||
bin/dwelling-radio -listen 127.0.0.1:18322 \
|
bin/dwelling-radio -listen 127.0.0.1:18322 \
|
||||||
-work-dir test \
|
-playlist /mnt/data/appdata/radio/playlists/all-rand \
|
||||||
-playlist test
|
-fallback-song /mnt/data/appdata/radio/fallback.ogg \
|
||||||
|
-db test.db3
|
||||||
|
|
||||||
install:
|
install:
|
||||||
install -Dm 0755 bin/${TARGET} ${DESTDIR}${PREFIX}/bin/${TARGET}
|
install -Dm 0755 bin/${TARGET} ${DESTDIR}${PREFIX}/bin/${TARGET}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
# Maintainer: Alexander "Arav" Andreev <me@arav.su>
|
||||||
pkgname=dwelling-radio
|
pkgname=dwelling-radio
|
||||||
pkgver=24.38.0
|
pkgver=24.25.0
|
||||||
pkgrel=1
|
pkgrel=1
|
||||||
pkgdesc="Arav's dwelling / Radio"
|
pkgdesc="Arav's dwelling / Radio"
|
||||||
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
arch=('i686' 'x86_64' 'arm' 'armv6h' 'armv7h' 'aarch64')
|
||||||
|
@ -1,30 +1,30 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
ihttp "dwelling-radio/internal/http"
|
ihttp "dwelling-radio/internal/http"
|
||||||
"dwelling-radio/internal/radio"
|
"dwelling-radio/internal/radio"
|
||||||
sqlite_stats "dwelling-radio/internal/statistics/db/sqlite"
|
sqlite_stats "dwelling-radio/internal/statistics/db/sqlite"
|
||||||
"dwelling-radio/pkg/utils"
|
"dwelling-radio/pkg/utils"
|
||||||
"dwelling-radio/web"
|
"dwelling-radio/web"
|
||||||
"dwelling-radio/web/locales"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"git.arav.su/Arav/httpr"
|
"git.arav.su/Arav/httpr"
|
||||||
"github.com/invopop/ctxi18n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
listenAddress = flag.String("listen", "/var/run/dwelling-radio/sock", "listen address (ip:port|unix_path)")
|
listenAddress = flag.String("listen", "/var/run/dwelling-radio/sock", "listen address (ip:port|unix_path)")
|
||||||
workDirPath = flag.String("work-dir", "/mnt/data/appdata/radio", "path to a working directory")
|
filelistPath = flag.String("filelist", "/mnt/data/appdata/radio/filelist.html", "path to a filelist.html file")
|
||||||
playlistName = flag.String("playlist", "all-rand", "a playlist name")
|
playlistPath = flag.String("playlist", "", "path to a playlist")
|
||||||
songListLen = flag.Int64("list-length", 10, "number of songs to show in last N songs table")
|
fallbackSong = flag.String("fallback-song", "", "path to a fallback song")
|
||||||
|
statisticsDbPath = flag.String("db", "/mnt/data/appdata/radio/statistics.db3", "path to a statistics database")
|
||||||
|
songListLen = flag.Int64("list-length", 10, "number of songs to show in last N songs table")
|
||||||
|
|
||||||
showVersion = flag.Bool("v", false, "show version")
|
showVersion = flag.Bool("v", false, "show version")
|
||||||
)
|
)
|
||||||
@ -40,7 +40,7 @@ func main() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := sqlite_stats.New(path.Join(*workDirPath, "statistics.db3"))
|
stats, err := sqlite_stats.New(*statisticsDbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Statistics:", err)
|
log.Fatalln("Statistics:", err)
|
||||||
}
|
}
|
||||||
@ -49,15 +49,11 @@ func main() {
|
|||||||
currentSong := radio.Song{}
|
currentSong := radio.Song{}
|
||||||
lstnrs := radio.NewListenerCounter()
|
lstnrs := radio.NewListenerCounter()
|
||||||
|
|
||||||
plylst, err := radio.NewPlaylist(path.Join(*workDirPath, "playlists", *playlistName), true)
|
plylst, err := radio.NewPlaylist(*playlistPath, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctxi18n.LoadWithDefault(locales.Content, "en"); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := httpr.New()
|
r := httpr.New()
|
||||||
|
|
||||||
r.Handler(http.MethodGet, "/", func(w http.ResponseWriter, r *http.Request) {
|
r.Handler(http.MethodGet, "/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -68,18 +64,18 @@ func main() {
|
|||||||
|
|
||||||
lstnrs.RLock()
|
lstnrs.RLock()
|
||||||
defer lstnrs.RUnlock()
|
defer lstnrs.RUnlock()
|
||||||
web.Index(version, ¤tSong, lst, *songListLen, lstnrs, r).Render(r.Context(), w)
|
web.Index(¤tSong, lst, *songListLen, lstnrs, r).Render(context.Background(), w)
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Handler(http.MethodGet, "/filelist", func(w http.ResponseWriter, r *http.Request) {
|
r.Handler(http.MethodGet, "/filelist", func(w http.ResponseWriter, r *http.Request) {
|
||||||
data, err := os.ReadFile(path.Join(*workDirPath, "filelist.html"))
|
if *filelistPath == "" {
|
||||||
if err != nil {
|
http.Error(w, "no filelist", http.StatusNotFound)
|
||||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
w.Header().Add("Link", "<"+utils.Site(r.Host)+"/filelist>; rel=\"canonical\"")
|
w.Header().Add("Link", "<"+utils.Site(r.Host)+"/filelist>; rel=\"canonical\"")
|
||||||
|
data, _ := os.ReadFile(*filelistPath)
|
||||||
w.Write(data)
|
w.Write(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -91,7 +87,7 @@ func main() {
|
|||||||
|
|
||||||
r.ServeStatic("/assets/*filepath", web.Assets())
|
r.ServeStatic("/assets/*filepath", web.Assets())
|
||||||
|
|
||||||
djh := ihttp.NewDJHandlers(lstnrs, plylst, stats, ¤tSong, *songListLen, path.Join(*workDirPath, "fallback.ogg"))
|
djh := ihttp.NewDJHandlers(lstnrs, plylst, stats, ¤tSong, *songListLen, *fallbackSong)
|
||||||
|
|
||||||
s := r.Sub("/api")
|
s := r.Sub("/api")
|
||||||
|
|
||||||
@ -99,7 +95,7 @@ func main() {
|
|||||||
s.Handler(http.MethodGet, "/playlist", djh.PlaylistNext)
|
s.Handler(http.MethodGet, "/playlist", djh.PlaylistNext)
|
||||||
s.Handler(http.MethodGet, "/status", djh.Status)
|
s.Handler(http.MethodGet, "/status", djh.Status)
|
||||||
|
|
||||||
srv := ihttp.NewHttpServer(I18nMiddleware(r))
|
srv := ihttp.NewHttpServer(r)
|
||||||
|
|
||||||
if err := srv.Start(*listenAddress); err != nil {
|
if err := srv.Start(*listenAddress); err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
@ -135,25 +131,3 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func I18nMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
lang := "en"
|
|
||||||
|
|
||||||
if lq := r.URL.Query().Get("lang"); lq != "" {
|
|
||||||
lc := http.Cookie{Name: "lang", Value: lq, HttpOnly: false, MaxAge: 0}
|
|
||||||
http.SetCookie(w, &lc)
|
|
||||||
lang = lq
|
|
||||||
} else if l, err := r.Cookie("lang"); err == nil {
|
|
||||||
lang = l.Value
|
|
||||||
} else if al := r.Header.Get("Accept-Language"); al != "" {
|
|
||||||
lang = r.Header.Get("Accept-Language")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, err := ctxi18n.WithLocale(r.Context(), lang)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("i18nmw:", err)
|
|
||||||
}
|
|
||||||
next.ServeHTTP(w, r.WithContext(ctx))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
12
go.mod
12
go.mod
@ -8,14 +8,6 @@ require github.com/pkg/errors v0.9.1
|
|||||||
|
|
||||||
require git.arav.su/Arav/httpr v0.3.2
|
require git.arav.su/Arav/httpr v0.3.2
|
||||||
|
|
||||||
require github.com/a-h/templ v0.2.778
|
require github.com/a-h/templ v0.2.680
|
||||||
|
|
||||||
require (
|
require github.com/mattn/go-sqlite3 v1.14.22
|
||||||
github.com/invopop/ctxi18n v0.8.1
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.23
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/invopop/yaml v0.3.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
|
||||||
|
22
go.sum
22
go.sum
@ -1,24 +1,10 @@
|
|||||||
git.arav.su/Arav/httpr v0.3.2 h1:a+ifu+9+FnQe6p/Kd4kgTDKAFN6zBOJjBTMjbAuHxVk=
|
git.arav.su/Arav/httpr v0.3.2 h1:a+ifu+9+FnQe6p/Kd4kgTDKAFN6zBOJjBTMjbAuHxVk=
|
||||||
git.arav.su/Arav/httpr v0.3.2/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
git.arav.su/Arav/httpr v0.3.2/go.mod h1:z0SVYwe5dBReeVuFU9QH2PmBxICJwchxqY5OfZbeVzU=
|
||||||
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
|
github.com/a-h/templ v0.2.680 h1:TflYFucxp5rmOxAXB9Xy3+QHTk8s8xG9+nCT/cLzjeE=
|
||||||
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
|
github.com/a-h/templ v0.2.680/go.mod h1:NQGQOycaPKBxRB14DmAaeIpcGC1AOBPJEMO4ozS7m90=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/invopop/ctxi18n v0.8.1 h1:nfy5Mk6UfvLbGRBwpTi4T1g95+rmRo8bMllUmpCvVwI=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/invopop/ctxi18n v0.8.1/go.mod h1:1Osw+JGYA+anHt0Z4reF36r5FtGHYjGQ+m1X7keIhPc=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso=
|
|
||||||
github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
|
@ -8,8 +8,10 @@ Type=simple
|
|||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
ExecStart=/usr/bin/dwelling-radio -listen /var/run/dwelling-radio/sock \
|
ExecStart=/usr/bin/dwelling-radio -listen /var/run/dwelling-radio/sock \
|
||||||
-work-dir /mnt/data/appdata/radio \
|
-filelist /mnt/data/appdata/radio/filelist.html \
|
||||||
-playlist all-rand \
|
-playlist /mnt/data/appdata/radio/playlists/all-rand \
|
||||||
|
-fallback-song /mnt/data/appdata/radio/fallback.ogg \
|
||||||
|
-db /mnt/data/appdata/radio/statistics.db3 \
|
||||||
-lst-len 10
|
-lst-len 10
|
||||||
|
|
||||||
ReadOnlyPaths=/
|
ReadOnlyPaths=/
|
||||||
|
@ -110,6 +110,8 @@ section { margin-top: 1rem; }
|
|||||||
|
|
||||||
#banner { text-align: center; }
|
#banner { text-align: center; }
|
||||||
|
|
||||||
|
#banner video { max-width: 90%; }
|
||||||
|
|
||||||
#player {
|
#player {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center; }
|
align-items: center; }
|
||||||
@ -158,10 +160,14 @@ footer {
|
|||||||
padding: 1rem 0; }
|
padding: 1rem 0; }
|
||||||
|
|
||||||
@media screen and (max-width: 640px) {
|
@media screen and (max-width: 640px) {
|
||||||
header {
|
header { display: block; }
|
||||||
align-items: center;
|
|
||||||
flex-direction: column; }
|
|
||||||
|
|
||||||
header svg { width: 100%; }
|
header svg {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%; }
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center; }
|
||||||
|
|
||||||
#player { flex-direction: column; } }
|
#player { flex-direction: column; } }
|
Before Width: | Height: | Size: 779 B After Width: | Height: | Size: 779 B |
@ -3,14 +3,12 @@ package web
|
|||||||
import "net/http"
|
import "net/http"
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
import "github.com/invopop/ctxi18n/i18n"
|
|
||||||
|
|
||||||
import "dwelling-radio/internal/radio"
|
import "dwelling-radio/internal/radio"
|
||||||
import "dwelling-radio/pkg/utils"
|
import "dwelling-radio/pkg/utils"
|
||||||
|
|
||||||
templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, lstnrs *radio.ListenerCounter, r *http.Request) {
|
templ Index(curSong *radio.Song, sl []radio.Song, slLen int64, lstnrs *radio.ListenerCounter, r *http.Request) {
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang={ i18n.GetLocale(ctx).Code().String() }>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
@ -18,11 +16,11 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
<meta name="theme-color" content="#cd2682" />
|
<meta name="theme-color" content="#cd2682" />
|
||||||
<meta name="color-scheme" content="light dark" />
|
<meta name="color-scheme" content="light dark" />
|
||||||
|
|
||||||
<title>Arav's dwelling / { i18n.T(ctx, "title") }</title>
|
<title>Arav's dwelling / Radio</title>
|
||||||
|
|
||||||
<meta name="author" content={ "Alexander \"Arav\" Andreev" } />
|
<meta name="author" content={ "Alexander \"Arav\" Andreev" } />
|
||||||
<meta name="description" content={ i18n.T(ctx, "description") } />
|
<meta name="description" content="Internet-radio broadcasting from under my desk." />
|
||||||
<meta name="keywords" content={ i18n.T(ctx, "keywords") } />
|
<meta name="keywords" content="self-host radio home-radio various music" />
|
||||||
|
|
||||||
<link rel="canonical" href={ utils.Site(r.Host) } />
|
<link rel="canonical" href={ utils.Site(r.Host) } />
|
||||||
|
|
||||||
@ -38,8 +36,8 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
<text y="25" textLength="360" lengthAdjust="spacingAndGlyphs">Welcome to my sacred place, wanderer</text>
|
<text y="25" textLength="360" lengthAdjust="spacingAndGlyphs">Welcome to my sacred place, wanderer</text>
|
||||||
</svg>
|
</svg>
|
||||||
<nav>
|
<nav>
|
||||||
<a href={ templ.SafeURL(utils.MainSite(r.Host)) }>{ i18n.T(ctx, "back-home") }</a>
|
<a href={ templ.SafeURL(utils.MainSite(r.Host)) }>Back to home</a>
|
||||||
<h1>{ i18n.T(ctx, "title") }</h1>
|
<h1>Radio</h1>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<section id="banner">
|
<section id="banner">
|
||||||
@ -49,9 +47,9 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<div class="small player-links">
|
<div class="small player-links">
|
||||||
<a href="/filelist">{ i18n.T(ctx, "link.filelist") }</a>
|
<a href="/filelist">filelist</a>
|
||||||
<a href="/playlist">{ i18n.T(ctx, "link.playlist") }</a>
|
<a href="/playlist">playlist</a>
|
||||||
<a href="/live/stream.ogg">{ i18n.T(ctx, "link.direct-link") }</a>
|
<a href="/live/stream.ogg">direct link</a>
|
||||||
(<a href="http://radio.arav.su:8000/stream.ogg">http</a>
|
(<a href="http://radio.arav.su:8000/stream.ogg">http</a>
|
||||||
<a href="http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg">Tor</a>
|
<a href="http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg">Tor</a>
|
||||||
<a href="http://radio.arav.i2p/live/stream.ogg">I2P</a>
|
<a href="http://radio.arav.i2p/live/stream.ogg">I2P</a>
|
||||||
@ -66,11 +64,11 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
</div>
|
</div>
|
||||||
<audio preload="none" controls playsinline>
|
<audio preload="none" controls playsinline>
|
||||||
<source src="/live/stream.ogg" type="audio/ogg" />
|
<source src="/live/stream.ogg" type="audio/ogg" />
|
||||||
{ i18n.T(ctx, "no-audio-tag") } <a href="/playlist">{ i18n.T(ctx, "link.playlist") }</a>.
|
Your browser doesn't support an audio element, it's sad... But you always can take the <a href="/playlist">playlist</a>!
|
||||||
</audio>
|
</audio>
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
<img src="/assets/img/headphones.svg" alt="Listeners" title="Listeners" />
|
<img src="/assets/img/listener.svg" alt="Listeners" title="Listeners" />
|
||||||
<span id="radio-listeners">{ strconv.FormatInt(lstnrs.Current(), 10) }</span>
|
<span id="radio-listeners">{ strconv.FormatInt(lstnrs.Current(), 10) }</span>
|
||||||
<img src="/assets/img/duration.svg" alt="Duration" title="Duration" />
|
<img src="/assets/img/duration.svg" alt="Duration" title="Duration" />
|
||||||
<span id="radio-duration-estimate"></span>
|
<span id="radio-duration-estimate"></span>
|
||||||
@ -83,7 +81,7 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<img src="/assets/img/note.svg" alt="Song" title="Song" />
|
<img src="/assets/img/note.svg" />
|
||||||
<span id="radio-song">
|
<span id="radio-song">
|
||||||
if curSong != nil && curSong.Artist != "" {
|
if curSong != nil && curSong.Artist != "" {
|
||||||
{ curSong.Artist } - { curSong.Title }
|
{ curSong.Artist } - { curSong.Title }
|
||||||
@ -94,13 +92,13 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>{ i18n.T(ctx, "last-songs.h", i18n.M{"n": strconv.FormatInt(slLen, 10)}) }</h2>
|
<h2>Last { strconv.FormatInt(slLen, 10) } songs</h2>
|
||||||
<table id="last-songs">
|
<table id="last-songs">
|
||||||
<thead class="small">
|
<thead class="small">
|
||||||
<tr>
|
<tr>
|
||||||
<td>{ i18n.T(ctx, "last-songs.tab-start") }</td>
|
<td>Start</td>
|
||||||
<td><abbr title={ i18n.T(ctx, "last-songs.tab-stat-tip") }>{ i18n.T(ctx, "last-songs.tab-stat") }</abbr></td>
|
<td><abbr title="Overall/Peak listeners">O/P</abbr></td>
|
||||||
<td>{ i18n.T(ctx, "last-songs.tab-song") }</td>
|
<td>Song</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -119,10 +117,7 @@ templ Index(prgVer string, curSong *radio.Song, sl []radio.Song, slLen int64, ls
|
|||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
<footer>
|
<footer>
|
||||||
<a href="?lang=ru">рус</a>
|
2017—2024 Alexander "Arav" Andreev <<a href="mailto:me@arav.su">me@arav.su</a>> <a href={ templ.SafeURL(utils.MainSite(r.Host) + "/privacy") }>Privacy statements</a>
|
||||||
<a href="?lang=en">eng</a>
|
|
||||||
<br/>
|
|
||||||
v{ prgVer } 2017—2024 { i18n.T(ctx, "footer.author") } <<a href="mailto:me@arav.su">me@arav.su</a>> <a href={ templ.SafeURL(utils.MainSite(r.Host) + "/privacy") }>{ i18n.T(ctx, "footer.privacy") }</a>
|
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
en:
|
|
||||||
title: Radio
|
|
||||||
description: Internet-radio broadcasting from under my desk.
|
|
||||||
keywords: self-host radio home-radio various music
|
|
||||||
back-home: Back home
|
|
||||||
link:
|
|
||||||
filelist: filelist
|
|
||||||
playlist: playlist
|
|
||||||
direct-link: direct link
|
|
||||||
no-audio-tag: Seems like your browser doesn't support an audio element, but you can grab the
|
|
||||||
last-songs:
|
|
||||||
h: Last %{n} songs
|
|
||||||
tab-start: Start
|
|
||||||
tab-stat: O/P
|
|
||||||
tab-stat-tip: Overall/Peak listeners
|
|
||||||
tab-song: Song
|
|
||||||
footer:
|
|
||||||
author: Alexander ❝Arav❞ Andreev
|
|
||||||
privacy: Privacy statements
|
|
@ -1,7 +0,0 @@
|
|||||||
package locales
|
|
||||||
|
|
||||||
import "embed"
|
|
||||||
|
|
||||||
//go:embed en
|
|
||||||
//go:embed ru
|
|
||||||
var Content embed.FS
|
|
@ -1,19 +0,0 @@
|
|||||||
ru:
|
|
||||||
title: Радио
|
|
||||||
description: Интернет-радио вещающееся из-под моего стола.
|
|
||||||
keywords: само-хост селф-хост радио разное музыка
|
|
||||||
back-home: Назад домой
|
|
||||||
link:
|
|
||||||
filelist: список файлов
|
|
||||||
playlist: плейлист
|
|
||||||
direct-link: прямая ссылка
|
|
||||||
no-audio-tag: Похоже на то, что твой браузер не поддерживает audio элемент, хреновенько, но можешь взять
|
|
||||||
last-songs:
|
|
||||||
h: Последние %{n} песен
|
|
||||||
tab-start: Начало
|
|
||||||
tab-stat: В/П
|
|
||||||
tab-stat-tip: Всего/Пиковое кол-во слушателей
|
|
||||||
tab-song: Песня
|
|
||||||
footer:
|
|
||||||
author: Александр «Arav» Андреев
|
|
||||||
privacy: О приватности
|
|
Loading…
Reference in New Issue
Block a user