diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..23141a3 --- /dev/null +++ b/.air.toml @@ -0,0 +1,58 @@ +#:schema https://json.schemastore.org/any.json + +env_files = [] +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + entrypoint = ["./tmp/main"] + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + ignore_dangerous_root_dir = false + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + app_start_timeout = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..75449c8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.woff2 filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index ec14f66..710c0af 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +tmp/ learning-title.txt diff --git a/go.mod b/go.mod index 7dd510e..e29cd74 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module codeberg.org/arimelody/ari-stream-utils +module codeberg.org/arimelody/ari-stream-tools go 1.26.4 @@ -16,6 +16,7 @@ require ( github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect + github.com/godbus/dbus/v5 v5.2.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/go.sum b/go.sum index 6a9c680..882bc10 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= +github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= diff --git a/learning/learning.go b/learning/learning.go index 139ed45..d93201b 100644 --- a/learning/learning.go +++ b/learning/learning.go @@ -3,7 +3,6 @@ package learning import ( "context" "embed" - "fmt" "io" "log" "net/http" @@ -12,7 +11,7 @@ import ( "sync" "time" - "codeberg.org/arimelody/ari-is-learning/broadcast" + "codeberg.org/arimelody/ari-stream-tools/broadcast" "github.com/gin-gonic/gin" ) @@ -29,7 +28,6 @@ type ( Service struct { cfg ServiceConfig - ctx context.Context titleFileMutex sync.Mutex titleLastModTime time.Time titleText string @@ -39,21 +37,25 @@ type ( ) func New(ctx context.Context, cfg ServiceConfig) *Service { + titleText := []byte{} titleUpdated := make(chan string) if _, err := os.Stat(cfg.TitleFilePath); os.IsNotExist(err) { log.Printf("Learning title file [%s] does not exist, creating one...", cfg.TitleFilePath) os.WriteFile(cfg.TitleFilePath, []byte("Untitled"), 0644) + titleText = []byte("Untitled") } else if err != nil { log.Fatalf("Failed to stat title file: %v", err) + } else { + titleText, err = os.ReadFile(cfg.TitleFilePath) + if err != nil { log.Fatalf("Failed to read title file: %v", err) } } srv := &Service{ cfg: cfg, - ctx: ctx, titleFileMutex: sync.Mutex{}, titleLastModTime: time.Unix(0, 0), - titleText: "", + titleText: string(titleText), titleUpdated: titleUpdated, titleUpdatedBroadcast: broadcast.NewBroadcastChannel(ctx, titleUpdated), } @@ -110,6 +112,8 @@ func (srv *Service) BindRoutes(group *gin.RouterGroup) { return } + srv.titleUpdated<-string(data) + ctx.String(http.StatusOK, http.StatusText(http.StatusOK)) }) @@ -123,45 +127,3 @@ func (srv *Service) BindRoutes(group *gin.RouterGroup) { http.ServeFileFS(ctx.Writer, ctx.Request, pagesFS, "pages/bye.html") }) } - -func (srv *Service) Run(ctx context.Context) { - failed := make(chan error) - - go func() { - for { - srv.titleFileMutex.Lock() - stat, err := os.Stat(srv.cfg.TitleFilePath) - if err != nil { - failed<-fmt.Errorf("Failed to stat title file: %v", err) - return - } - - if srv.titleLastModTime.Before(stat.ModTime()) { - data, err := os.ReadFile(srv.cfg.TitleFilePath) - if err != nil { - failed<-fmt.Errorf("Failed to read title file: %v", err) - return - } - - log.Printf( - "title updated: \"%s\" -> \"%s\"", - srv.titleText, - strings.TrimSpace(string(data)), - ) - - srv.titleText = strings.TrimSpace(string(data)) - if !srv.titleLastModTime.IsZero() { srv.titleUpdated<-srv.titleText } - srv.titleLastModTime = stat.ModTime() - } - srv.titleFileMutex.Unlock() - - time.Sleep(100 * time.Millisecond) - } - }() - - select { - case err := <-failed: - log.Fatalf("Learning service error: %v", err) - case <-ctx.Done(): - } -} diff --git a/main.go b/main.go index c0ce017..33a9a51 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,8 @@ import ( "strconv" "syscall" - "codeberg.org/arimelody/ari-stream-utils/learning" + "codeberg.org/arimelody/ari-stream-tools/learning" + "codeberg.org/arimelody/ari-stream-tools/music" "github.com/gin-gonic/gin" ) @@ -41,11 +42,13 @@ func main() { learningService := learning.New(ctx, learning.ServiceConfig{ TitleFilePath: "learning-title.txt", }) + musicService := music.New(ctx) srv := gin.Default() learningService.BindRoutes(srv.Group("/learning")) + musicService.BindRoutes(srv.Group("/music")) - go learningService.Run(ctx) + go musicService.Run(ctx) go func() { log.Printf("Now serving at http://%s:%d\n", host, port) failed <- srv.Run(fmt.Sprintf("%s:%d", host, port)) diff --git a/mpris/mpris.go b/mpris/mpris.go deleted file mode 100644 index 31d5fb9..0000000 --- a/mpris/mpris.go +++ /dev/null @@ -1 +0,0 @@ -package mpris diff --git a/music/music.go b/music/music.go new file mode 100644 index 0000000..3f9c3a5 --- /dev/null +++ b/music/music.go @@ -0,0 +1,292 @@ +package music + +import ( + "context" + "embed" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" + + "codeberg.org/arimelody/ari-stream-tools/broadcast" + "github.com/gin-gonic/gin" + "github.com/godbus/dbus/v5" +) + +const ( + MPRIS_ART_URL = "mpris:artUrl" + MPRIS_LENGTH = "mpris:length" + MPRIS_TRACK_ID = "mpris:trackid" + XESAM_TITLE = "xesam:title" + XESAM_ALBUM = "xesam:album" + XESAM_ARTISTS = "xesam:artist" + + DBUS_STRAWBERRY = "org.mpris.MediaPlayer2.strawberry" +) + +//go:embed public +var publicFS embed.FS +//go:embed pages +var pagesFS embed.FS + +type ( + trackMetadata struct { + ArtworkURL string + Length int64 + Title string + Album string + Artists []string + } + + Service struct { + track *trackMetadata + nextTrack chan *trackMetadata + nextTrackBroadcast broadcast.BroadcastChannel[*trackMetadata] + playing bool + nextPlaying chan bool + playingBroadcast broadcast.BroadcastChannel[bool] + } +) + +func New(ctx context.Context) *Service { + nextTrack := make(chan *trackMetadata) + nextPlaying := make(chan bool) + + srv := &Service{ + track: nil, + nextTrack: nextTrack, + nextTrackBroadcast: broadcast.NewBroadcastChannel(ctx, nextTrack), + playing: false, + nextPlaying: nextPlaying, + playingBroadcast: broadcast.NewBroadcastChannel(ctx, nextPlaying), + } + + return srv +} + +func (srv *Service) BindRoutes(group *gin.RouterGroup) { + group.GET("/public/*path", func(ctx *gin.Context) { + path := strings.TrimPrefix(ctx.Request.URL.Path, "/music/") + http.ServeFileFS(ctx.Writer, ctx.Request, publicFS, path) + }) + + group.GET("/sse", func(ctx *gin.Context) { + ctx.Header("connection", "keep-alive") + + nextTrack := srv.nextTrackBroadcast.Subscribe() + nextPlaying := srv.playingBroadcast.Subscribe() + + if srv.track == nil { <-nextTrack } + + ctx.SSEvent("music", gin.H{ + "title": srv.track.Title, + "artist": artistString(srv.track), + }) + ctx.SSEvent("music-state", gin.H{ "playing": srv.playing }) + + ticker := time.NewTicker(10 * time.Millisecond) + ctx.Stream(func(w io.Writer) bool { + select { + case track := <-nextTrack: + ctx.SSEvent("music", gin.H{ + "title": track.Title, + "artist": artistString(track), + }) + case playing := <-nextPlaying: + ctx.SSEvent("music-state", gin.H{ "playing": playing }) + case <-ticker.C: + } + return true + }) + + srv.nextTrackBroadcast.Cancel(nextTrack) + srv.playingBroadcast.Cancel(nextPlaying) + }) + + group.GET("/artwork", func(ctx *gin.Context) { + ctx.Header("cache-control", "no-cache") + + if srv.track == nil || srv.track.ArtworkURL == "" { + http.ServeFileFS(ctx.Writer, ctx.Request, publicFS, "public/default-cover-art.png") + return + } + + artworkPath := strings.TrimPrefix(srv.track.ArtworkURL, "file://") + http.ServeFile(ctx.Writer, ctx.Request, artworkPath) + }) + + group.GET("/", func(ctx *gin.Context) { + http.ServeFileFS(ctx.Writer, ctx.Request, pagesFS, "pages/music.html") + }) +} + +func (srv *Service) Run(ctx context.Context) { + var err error + + conn, err := dbus.ConnectSessionBus() + if err != nil { + log.Fatalf("Failed to connect to session bus: %v", err) + } + defer conn.Close() + + var data map[string]dbus.Variant + obj := conn.Object(DBUS_STRAWBERRY, "/org/mpris/MediaPlayer2") + if err := obj.Call( + "org.freedesktop.DBus.Properties.GetAll", + 0, + "org.mpris.MediaPlayer2.Player", + ).Store(&data); err != nil { + log.Fatalf("Failed to query media player data: %v", err) + } + + metadata, ok := data["Metadata"].Value().(map[string]dbus.Variant) + if !ok { log.Fatalf("Could not cast metadata to map[string]dbus.Variant") } + + playbackStatus, ok := data["PlaybackStatus"].Value().(string) + if !ok { log.Fatalf("Could not cast metadata to string") } + + // on first run, mpris:artUrl returns a temporary strawberry player artwork + // located in /tmp. this is gone by the time we attempt to read, so we need + // a way to pull the real artwork filepath at runtime. + // TODO: figure out how to pull real (not /tmp) track artwork + if srv.track, err = parseTrackMetadata(metadata); err == nil { + log.Printf("Now playing: %s - %s", artistString(srv.track), srv.track.Title) + } else { + log.Printf("Failed to parse track metadata: %v", err) + } + srv.playing = playbackStatus == "Playing" + + var dbusChan chan *dbus.Signal + dbusChan, err = setupPlayerListener(conn) + if err != nil { + log.Fatalf("Failed to connect to session bus: %v", err) + } + + failed := make(chan error) + go func() { + for { + for signal := range dbusChan { + data, ok := signal.Body[1].(map[string]dbus.Variant) + if !ok { continue } + + if _, ok := data["PlaybackStatus"]; ok { + playbackStatus, ok := data["PlaybackStatus"].Value().(string) + if ok { + if playbackStatus == "Playing" { + srv.playing = true + log.Print("Music playing") + } else { + srv.playing = false + log.Print("Music paused") + } + srv.nextPlaying<-srv.playing + } + + } else if _, ok := data["Metadata"]; ok { + metadata, ok := data["Metadata"].Value().(map[string]dbus.Variant) + if !ok { continue } + + nextTrack, err := parseTrackMetadata(metadata) + if err != nil { + log.Printf("Failed to parse track metadata: %v", err) + continue + } + /* + if nextTrack.Title == srv.track.Title && + slices.Compare(nextTrack.Artists, srv.track.Artists) == 0 { + continue + } + */ + srv.track = nextTrack + srv.nextTrack<-nextTrack + + log.Printf("Now playing: %s - %s", artistString(nextTrack), nextTrack.Title) + + } + } + + log.Printf("Session bus connection lost. Reconnecting...") + conn, err = dbus.ConnectSessionBus() + dbusChan, err = setupPlayerListener(conn) + if err != nil { + log.Fatalf("Failed to connect to session bus: %v", err) + } + + time.Sleep(1 * time.Second) + } + }() + + select { + case err := <-failed: + log.Fatalf("MPRIS/D-Bus error: %v", err) + case <-ctx.Done(): + } +} + +func parseTrackMetadata(metadata map[string]dbus.Variant) (*trackMetadata, error) { + if _, exists := metadata[MPRIS_LENGTH]; !exists { + return nil, fmt.Errorf("Metadata does not contain %s", MPRIS_LENGTH) + } + length, ok := metadata[MPRIS_LENGTH].Value().(int64) + if !ok { return nil, fmt.Errorf("Failed to cast %s to int64", MPRIS_LENGTH) } + + var artUrl string + if _, exists := metadata[MPRIS_ART_URL]; exists { + artUrl, ok = metadata[MPRIS_ART_URL].Value().(string) + if !ok { return nil, fmt.Errorf("Failed to cast %s to string", MPRIS_ART_URL) } + } + + if _, exists := metadata[XESAM_TITLE]; !exists { + return nil, fmt.Errorf("Metadata does not contain %s", XESAM_TITLE) + } + title, ok := metadata[XESAM_TITLE].Value().(string) + if !ok { return nil, fmt.Errorf("Failed to cast %s to string", XESAM_TITLE) } + + var album string + if _, exists := metadata[XESAM_ALBUM]; exists { + album, ok = metadata[XESAM_ALBUM].Value().(string) + if !ok { return nil, fmt.Errorf("Failed to cast %s to string", XESAM_ALBUM) } + } + + var artists []string + if _, exists := metadata[XESAM_ARTISTS]; exists { + artists, ok = metadata[XESAM_ARTISTS].Value().([]string) + if !ok { return nil, fmt.Errorf("Failed to cast %s to []string", XESAM_ARTISTS) } + } + + trackMetadata := &trackMetadata{ + ArtworkURL: artUrl, + Length: length, + Title: title, + Album: album, + Artists: artists, + } + + return trackMetadata, nil +} + +func artistString(track *trackMetadata) string { + artists := "" + if len(track.Artists) == 0 { return "Unknown Artist" } + if len(track.Artists) == 1 { return track.Artists[0] } + artists += strings.Join(track.Artists[:len(track.Artists)-2], ", ") + artists += " & " + track.Artists[len(track.Artists) - 1] + return artists +} + +func setupPlayerListener(conn *dbus.Conn) (chan *dbus.Signal, error) { + if err := conn.AddMatchSignal( + dbus.WithMatchObjectPath("/org/mpris/MediaPlayer2"), + dbus.WithMatchInterface("org.freedesktop.DBus.Properties"), + dbus.WithMatchSender(DBUS_STRAWBERRY), + ); err != nil { + return nil, fmt.Errorf("Failed to configure D-Bus signal listener: %v", err) + } + + c := make(chan *dbus.Signal) + conn.Signal(c) + + return c, nil +} diff --git a/music/pages/music.html b/music/pages/music.html new file mode 100644 index 0000000..0f854af --- /dev/null +++ b/music/pages/music.html @@ -0,0 +1,31 @@ + + + + + + Nothing is playing... + + + + +
+
+ + +
+ song artwork + +
+

Untitled Track

+

Unknown Artist

+
+
+
+
+ +
+ + diff --git a/music/public/default-cover-art.png b/music/public/default-cover-art.png new file mode 100755 index 0000000..8e19b8c Binary files /dev/null and b/music/public/default-cover-art.png differ diff --git a/music/public/fonts/inter/Inter-Black.woff2 b/music/public/fonts/inter/Inter-Black.woff2 new file mode 100644 index 0000000..89b8df9 --- /dev/null +++ b/music/public/fonts/inter/Inter-Black.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12ed0eed6749099b46c7b2e8198dc30c2d7e0f2a4e5fb1d12f0b6ae2c4f33cc4 +size 111668 diff --git a/music/public/fonts/inter/Inter-BlackItalic.woff2 b/music/public/fonts/inter/Inter-BlackItalic.woff2 new file mode 100644 index 0000000..71f56ab --- /dev/null +++ b/music/public/fonts/inter/Inter-BlackItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:308e101141e9dee3b6b121614da03969eb8179886975a9eeb5348021b04368fe +size 118420 diff --git a/music/public/fonts/inter/Inter-Bold.woff2 b/music/public/fonts/inter/Inter-Bold.woff2 new file mode 100644 index 0000000..07604e9 --- /dev/null +++ b/music/public/fonts/inter/Inter-Bold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa888127b6da015b65569f0351f3b5c391ad928904951f1c20e9f8462a8d95ea +size 114840 diff --git a/music/public/fonts/inter/Inter-BoldItalic.woff2 b/music/public/fonts/inter/Inter-BoldItalic.woff2 new file mode 100644 index 0000000..ac216a9 --- /dev/null +++ b/music/public/fonts/inter/Inter-BoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a10532d76c90c4597c0ad4bfc9284600e597d3012dd1ec16c1d44e0ad0058ab +size 121500 diff --git a/music/public/fonts/inter/Inter-ExtraBold.woff2 b/music/public/fonts/inter/Inter-ExtraBold.woff2 new file mode 100644 index 0000000..1f88168 --- /dev/null +++ b/music/public/fonts/inter/Inter-ExtraBold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f75025856f8db1b2186e9cb89be9de9894932c8b7b20f4df5e65916ff714e34 +size 114856 diff --git a/music/public/fonts/inter/Inter-ExtraBoldItalic.woff2 b/music/public/fonts/inter/Inter-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..25e52ce --- /dev/null +++ b/music/public/fonts/inter/Inter-ExtraBoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53bb20ba292e2ac28a0777bf0f56f31b98be432a3cc04359e2eb608c758349c7 +size 121516 diff --git a/music/public/fonts/inter/Inter-ExtraLight.woff2 b/music/public/fonts/inter/Inter-ExtraLight.woff2 new file mode 100644 index 0000000..1ab4d09 --- /dev/null +++ b/music/public/fonts/inter/Inter-ExtraLight.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba4fc81dbb25871f1bcabc664b1e37703fca0a05f7248a923e7db497c6d211cc +size 112728 diff --git a/music/public/fonts/inter/Inter-ExtraLightItalic.woff2 b/music/public/fonts/inter/Inter-ExtraLightItalic.woff2 new file mode 100644 index 0000000..e88cd22 --- /dev/null +++ b/music/public/fonts/inter/Inter-ExtraLightItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:38bad3a6a9ad1a7fcd8082e6423ba5e0baea16598f2cf304e527844f697e32b2 +size 119320 diff --git a/music/public/fonts/inter/Inter-Italic.woff2 b/music/public/fonts/inter/Inter-Italic.woff2 new file mode 100644 index 0000000..6b36b21 --- /dev/null +++ b/music/public/fonts/inter/Inter-Italic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2d078cb3bc8f934740d53b39dd23b0678f2f97477e49ec785dd9d8acd8b96bfc +size 117700 diff --git a/music/public/fonts/inter/Inter-Light.woff2 b/music/public/fonts/inter/Inter-Light.woff2 new file mode 100644 index 0000000..1feb0f1 --- /dev/null +++ b/music/public/fonts/inter/Inter-Light.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e111a1e2ad914ccda9179b95e83fb10234dd52a1932e0b93c480476227983fd9 +size 112592 diff --git a/music/public/fonts/inter/Inter-LightItalic.woff2 b/music/public/fonts/inter/Inter-LightItalic.woff2 new file mode 100644 index 0000000..5d65227 --- /dev/null +++ b/music/public/fonts/inter/Inter-LightItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:050265b27d11860a26c66c054210852661abb5d229c4e0bb3525757a830f3790 +size 119608 diff --git a/music/public/fonts/inter/Inter-Medium.woff2 b/music/public/fonts/inter/Inter-Medium.woff2 new file mode 100644 index 0000000..f6354b1 --- /dev/null +++ b/music/public/fonts/inter/Inter-Medium.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ff3e94614e1493eb556314fd247ae6c4a85a7783b4cc86be539940cf83f2a48 +size 114348 diff --git a/music/public/fonts/inter/Inter-MediumItalic.woff2 b/music/public/fonts/inter/Inter-MediumItalic.woff2 new file mode 100644 index 0000000..f578443 --- /dev/null +++ b/music/public/fonts/inter/Inter-MediumItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1b0df503de488a92082a8c5d72ddc4c0229183ceb645ae1c25c2d0d63778517c +size 120784 diff --git a/music/public/fonts/inter/Inter-Regular.woff2 b/music/public/fonts/inter/Inter-Regular.woff2 new file mode 100644 index 0000000..cd2b9fe --- /dev/null +++ b/music/public/fonts/inter/Inter-Regular.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e06f6b1bc553aaea4e4668023ed0ab0a147129c3107f511bc7d03d361b0ae085 +size 111268 diff --git a/music/public/fonts/inter/Inter-SemiBold.woff2 b/music/public/fonts/inter/Inter-SemiBold.woff2 new file mode 100644 index 0000000..6d48aeb --- /dev/null +++ b/music/public/fonts/inter/Inter-SemiBold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5cb7103e4e605989afebc03d989c79201e54b21b5183db33981f70db9178a301 +size 114812 diff --git a/music/public/fonts/inter/Inter-SemiBoldItalic.woff2 b/music/public/fonts/inter/Inter-SemiBoldItalic.woff2 new file mode 100644 index 0000000..89d1089 --- /dev/null +++ b/music/public/fonts/inter/Inter-SemiBoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e2a2bc3b5b9af0644f005693b10499dab18065d6aedbf46c8b74ccede27daeb +size 121416 diff --git a/music/public/fonts/inter/Inter-Thin.woff2 b/music/public/fonts/inter/Inter-Thin.woff2 new file mode 100644 index 0000000..8607f76 --- /dev/null +++ b/music/public/fonts/inter/Inter-Thin.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70ca998635d9fc627dede8108f04d0989e6e03346183f0ad0917723e790f6973 +size 109548 diff --git a/music/public/fonts/inter/Inter-ThinItalic.woff2 b/music/public/fonts/inter/Inter-ThinItalic.woff2 new file mode 100644 index 0000000..5637118 --- /dev/null +++ b/music/public/fonts/inter/Inter-ThinItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:930d666b74e0dc0c26c2821f5643b701f8a41beaf3bb95ce146298104f29169c +size 116880 diff --git a/music/public/fonts/inter/InterDisplay-Black.woff2 b/music/public/fonts/inter/InterDisplay-Black.woff2 new file mode 100644 index 0000000..d7dbac2 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Black.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05650cb83c70fda2a465eade0c0a1a6c25d37ec49df3cd74b7e564683f9729f1 +size 110308 diff --git a/music/public/fonts/inter/InterDisplay-BlackItalic.woff2 b/music/public/fonts/inter/InterDisplay-BlackItalic.woff2 new file mode 100644 index 0000000..98950fe --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-BlackItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9746bf2bd43fe158144b83ede747088e80396daad55b9e6ea7232e0e22ee6fce +size 116820 diff --git a/music/public/fonts/inter/InterDisplay-Bold.woff2 b/music/public/fonts/inter/InterDisplay-Bold.woff2 new file mode 100644 index 0000000..c6992e4 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Bold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:23bc37619593377e128f24660fedb2869d18277b4026cb46e5637be7643faf91 +size 113556 diff --git a/music/public/fonts/inter/InterDisplay-BoldItalic.woff2 b/music/public/fonts/inter/InterDisplay-BoldItalic.woff2 new file mode 100644 index 0000000..30c6bfb --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-BoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5a0fe1f01b8777fab4a5f73c757743fa52579c7723b0291777625cb53c0a792a +size 120432 diff --git a/music/public/fonts/inter/InterDisplay-ExtraBold.woff2 b/music/public/fonts/inter/InterDisplay-ExtraBold.woff2 new file mode 100644 index 0000000..a426fdb --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-ExtraBold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:97eeee5a6f4f22fef1f607cf8836104d65968f03e59bc068a7586bf18364a8a7 +size 113636 diff --git a/music/public/fonts/inter/InterDisplay-ExtraBoldItalic.woff2 b/music/public/fonts/inter/InterDisplay-ExtraBoldItalic.woff2 new file mode 100644 index 0000000..7358e69 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-ExtraBoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:159b72ba47887032915420aa1445ae6768197e7ea732230182998f14d1cdb932 +size 120712 diff --git a/music/public/fonts/inter/InterDisplay-ExtraLight.woff2 b/music/public/fonts/inter/InterDisplay-ExtraLight.woff2 new file mode 100644 index 0000000..f93b602 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-ExtraLight.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9031b6d2a1a559753d50b311ecb463ddf7acaaae1d992a7e9b009620d8ce6def +size 113108 diff --git a/music/public/fonts/inter/InterDisplay-ExtraLightItalic.woff2 b/music/public/fonts/inter/InterDisplay-ExtraLightItalic.woff2 new file mode 100644 index 0000000..0c749bb --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-ExtraLightItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e8d7078160ef92b82c2123857c1b3aefaa4a795a44b56df5fe3edade60d81576 +size 120124 diff --git a/music/public/fonts/inter/InterDisplay-Italic.woff2 b/music/public/fonts/inter/InterDisplay-Italic.woff2 new file mode 100644 index 0000000..4d6a0e4 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Italic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dfd398d7372b61278e1505bfbdfc5ab0f6511ef887a47a2c90c13475eae585da +size 116988 diff --git a/music/public/fonts/inter/InterDisplay-Light.woff2 b/music/public/fonts/inter/InterDisplay-Light.woff2 new file mode 100644 index 0000000..c1cdf65 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Light.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:01dd2d491ed863177e61f6e9458813baa85453ad97b32c5c1216927d4eebfe65 +size 112976 diff --git a/music/public/fonts/inter/InterDisplay-LightItalic.woff2 b/music/public/fonts/inter/InterDisplay-LightItalic.woff2 new file mode 100644 index 0000000..84336f7 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-LightItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59b5be56306fd353d067aa6995c2c92bc3feb3ace73684d94e3c6e73fe2beaac +size 119692 diff --git a/music/public/fonts/inter/InterDisplay-Medium.woff2 b/music/public/fonts/inter/InterDisplay-Medium.woff2 new file mode 100644 index 0000000..cccb58b --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Medium.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1227907684853882ad00d7f97ce9f64bc17b89a2a291a7d4ec84fccfa442934 +size 113476 diff --git a/music/public/fonts/inter/InterDisplay-MediumItalic.woff2 b/music/public/fonts/inter/InterDisplay-MediumItalic.woff2 new file mode 100644 index 0000000..7d96b60 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-MediumItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bf254c1a65c806c019f52818add66747da83595a314112917911bcf5fa2f5c5 +size 120560 diff --git a/music/public/fonts/inter/InterDisplay-Regular.woff2 b/music/public/fonts/inter/InterDisplay-Regular.woff2 new file mode 100644 index 0000000..8112f1d --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Regular.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a9463a58c3e7ba1e3cd65b5dbff91a35c508ff78a104cd1121feff83efeb787 +size 108948 diff --git a/music/public/fonts/inter/InterDisplay-SemiBold.woff2 b/music/public/fonts/inter/InterDisplay-SemiBold.woff2 new file mode 100644 index 0000000..5b42dac --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-SemiBold.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d9f63a82b826fb0117c92715c3a52a2d2247bc321bc39341420bf52d91e8277a +size 113772 diff --git a/music/public/fonts/inter/InterDisplay-SemiBoldItalic.woff2 b/music/public/fonts/inter/InterDisplay-SemiBoldItalic.woff2 new file mode 100644 index 0000000..257b0fa --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-SemiBoldItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd9d78314bdd5062302a73c7e2954008bab8158304fa7ac4f0ea8977b841dccb +size 120468 diff --git a/music/public/fonts/inter/InterDisplay-Thin.woff2 b/music/public/fonts/inter/InterDisplay-Thin.woff2 new file mode 100644 index 0000000..18ede47 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-Thin.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21cfe42e7f1031b410e247ed199f43fc362415af81b3a8e442a97f63b4dbe327 +size 108820 diff --git a/music/public/fonts/inter/InterDisplay-ThinItalic.woff2 b/music/public/fonts/inter/InterDisplay-ThinItalic.woff2 new file mode 100644 index 0000000..8b58c68 --- /dev/null +++ b/music/public/fonts/inter/InterDisplay-ThinItalic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eff21589eacf1f6a596191038d7b47b4dd2455a096b175bb3572ecb6d026a611 +size 116040 diff --git a/music/public/fonts/inter/InterVariable-Italic.woff2 b/music/public/fonts/inter/InterVariable-Italic.woff2 new file mode 100644 index 0000000..318e720 --- /dev/null +++ b/music/public/fonts/inter/InterVariable-Italic.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e564f652916db6c139570fefb9524a77c4d48f30c92928de9db19b6b5c7a262a +size 387976 diff --git a/music/public/fonts/inter/InterVariable.woff2 b/music/public/fonts/inter/InterVariable.woff2 new file mode 100644 index 0000000..e511e5e --- /dev/null +++ b/music/public/fonts/inter/InterVariable.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:693b77d4f32ee9b8bfc995589b5fad5e99adf2832738661f5402f9978429a8e3 +size 352240 diff --git a/music/public/fonts/inter/LICENSE.txt b/music/public/fonts/inter/LICENSE.txt new file mode 100644 index 0000000..9b2ca37 --- /dev/null +++ b/music/public/fonts/inter/LICENSE.txt @@ -0,0 +1,92 @@ +Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION AND CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/music/public/fonts/inter/inter.css b/music/public/fonts/inter/inter.css new file mode 100644 index 0000000..6144065 --- /dev/null +++ b/music/public/fonts/inter/inter.css @@ -0,0 +1,148 @@ +/* Variable fonts usage: +:root { font-family: "Inter", sans-serif; } +@supports (font-variation-settings: normal) { + :root { font-family: "InterVariable", sans-serif; font-optical-sizing: auto; } +} */ +@font-face { + font-family: InterVariable; + font-style: normal; + font-weight: 100 900; + font-display: swap; + src: url("InterVariable.woff2") format("woff2"); +} +@font-face { + font-family: InterVariable; + font-style: italic; + font-weight: 100 900; + font-display: swap; + src: url("InterVariable-Italic.woff2") format("woff2"); +} + +/* static fonts */ +@font-face { font-family: "Inter"; font-style: normal; font-weight: 100; font-display: swap; src: url("Inter-Thin.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 100; font-display: swap; src: url("Inter-ThinItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 200; font-display: swap; src: url("Inter-ExtraLight.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 200; font-display: swap; src: url("Inter-ExtraLightItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 300; font-display: swap; src: url("Inter-Light.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 300; font-display: swap; src: url("Inter-LightItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 400; font-display: swap; src: url("Inter-Regular.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 400; font-display: swap; src: url("Inter-Italic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 500; font-display: swap; src: url("Inter-Medium.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 500; font-display: swap; src: url("Inter-MediumItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 600; font-display: swap; src: url("Inter-SemiBold.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 600; font-display: swap; src: url("Inter-SemiBoldItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 700; font-display: swap; src: url("Inter-Bold.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 700; font-display: swap; src: url("Inter-BoldItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 800; font-display: swap; src: url("Inter-ExtraBold.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 800; font-display: swap; src: url("Inter-ExtraBoldItalic.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: normal; font-weight: 900; font-display: swap; src: url("Inter-Black.woff2") format("woff2"); } +@font-face { font-family: "Inter"; font-style: italic; font-weight: 900; font-display: swap; src: url("Inter-BlackItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 100; font-display: swap; src: url("InterDisplay-Thin.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 100; font-display: swap; src: url("InterDisplay-ThinItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLight.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 200; font-display: swap; src: url("InterDisplay-ExtraLightItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 300; font-display: swap; src: url("InterDisplay-Light.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 300; font-display: swap; src: url("InterDisplay-LightItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 400; font-display: swap; src: url("InterDisplay-Regular.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 400; font-display: swap; src: url("InterDisplay-Italic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 500; font-display: swap; src: url("InterDisplay-Medium.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 500; font-display: swap; src: url("InterDisplay-MediumItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBold.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 600; font-display: swap; src: url("InterDisplay-SemiBoldItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 700; font-display: swap; src: url("InterDisplay-Bold.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 700; font-display: swap; src: url("InterDisplay-BoldItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBold.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 800; font-display: swap; src: url("InterDisplay-ExtraBoldItalic.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: normal; font-weight: 900; font-display: swap; src: url("InterDisplay-Black.woff2") format("woff2"); } +@font-face { font-family: "InterDisplay"; font-style: italic; font-weight: 900; font-display: swap; src: url("InterDisplay-BlackItalic.woff2") format("woff2"); } + +@font-feature-values InterVariable { + @character-variant { + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; + alt-1: 1; /* Alternate one */ + alt-3: 9; /* Flat-top three */ + open-4: 2; /* Open four */ + open-6: 3; /* Open six */ + open-9: 4; /* Open nine */ + lc-l-with-tail: 5; /* Lower-case L with tail */ + simplified-u: 6; /* Simplified u */ + alt-double-s: 7; /* Alternate German double s */ + uc-i-with-serif: 8; /* Upper-case i with serif */ + uc-g-with-spur: 10; /* Capital G with spur */ + single-story-a: 11; /* Single-story a */ + compact-lc-f: 12; /* Compact f */ + compact-lc-t: 13; /* Compact t */ + } + @styleset { + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; + open-digits: 1; /* Open digits */ + disambiguation: 2; /* Disambiguation (with zero) */ + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ + round-quotes-and-commas: 3; /* Round quotes & commas */ + square-punctuation: 7; /* Square punctuation */ + square-quotes: 8; /* Square quotes */ + circled-characters: 5; /* Circled characters */ + squared-characters: 6; /* Squared characters */ + } +} +@font-feature-values Inter { + @character-variant { + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; + alt-1: 1; /* Alternate one */ + alt-3: 9; /* Flat-top three */ + open-4: 2; /* Open four */ + open-6: 3; /* Open six */ + open-9: 4; /* Open nine */ + lc-l-with-tail: 5; /* Lower-case L with tail */ + simplified-u: 6; /* Simplified u */ + alt-double-s: 7; /* Alternate German double s */ + uc-i-with-serif: 8; /* Upper-case i with serif */ + uc-g-with-spur: 10; /* Capital G with spur */ + single-story-a: 11; /* Single-story a */ + compact-lc-f: 12; /* Compact f */ + compact-lc-t: 13; /* Compact t */ + } + @styleset { + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; + open-digits: 1; /* Open digits */ + disambiguation: 2; /* Disambiguation (with zero) */ + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ + round-quotes-and-commas: 3; /* Round quotes & commas */ + square-punctuation: 7; /* Square punctuation */ + square-quotes: 8; /* Square quotes */ + circled-characters: 5; /* Circled characters */ + squared-characters: 6; /* Squared characters */ + } +} +@font-feature-values InterDisplay { + @character-variant { + cv01: 1; cv02: 2; cv03: 3; cv04: 4; cv05: 5; cv06: 6; cv07: 7; cv08: 8; + cv09: 9; cv10: 10; cv11: 11; cv12: 12; cv13: 13; + alt-1: 1; /* Alternate one */ + alt-3: 9; /* Flat-top three */ + open-4: 2; /* Open four */ + open-6: 3; /* Open six */ + open-9: 4; /* Open nine */ + lc-l-with-tail: 5; /* Lower-case L with tail */ + simplified-u: 6; /* Simplified u */ + alt-double-s: 7; /* Alternate German double s */ + uc-i-with-serif: 8; /* Upper-case i with serif */ + uc-g-with-spur: 10; /* Capital G with spur */ + single-story-a: 11; /* Single-story a */ + compact-lc-f: 12; /* Compact f */ + compact-lc-t: 13; /* Compact t */ + } + @styleset { + ss01: 1; ss02: 2; ss03: 3; ss04: 4; ss05: 5; ss06: 6; ss07: 7; ss08: 8; + open-digits: 1; /* Open digits */ + disambiguation: 2; /* Disambiguation (with zero) */ + disambiguation-except-zero: 4; /* Disambiguation (no zero) */ + round-quotes-and-commas: 3; /* Round quotes & commas */ + square-punctuation: 7; /* Square punctuation */ + square-quotes: 8; /* Square quotes */ + circled-characters: 5; /* Circled characters */ + squared-characters: 6; /* Squared characters */ + } +} diff --git a/music/public/music.css b/music/public/music.css new file mode 100644 index 0000000..f86e398 --- /dev/null +++ b/music/public/music.css @@ -0,0 +1,120 @@ +:root { + --sp-1: 1px; + --sp-2: 2px; + --sp-3: 4px; + --sp-4: 8px; + --sp-5: 16px; + + --col-fg: #ffffff; + + --smooth: cubic-bezier(.1, 1, .5, 1); +} + +body { + width: 100%; + height: 100vh; + margin: 0; + font-family: Inter, sans-serif; + font-feature-settings: 'liga' 1, 'calt' 1; + + color: var(--col-fg); + background-color: transparent; + + text-shadow: + var(--sp-1) var(--sp-1) var(--sp-4) #000, + var(--sp-1) var(--sp-2) #0008; +} +@supports (font-variation-settings: normal) { + body { font-family: InterVariable, sans-serif; } +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.flex-row { + flex-direction: row; +} + +.justify-center { + justify-content: center; +} + +.gap-1 { gap: var(--sp-1); } +.gap-2 { gap: var(--sp-2); } +.gap-3 { gap: var(--sp-3); } +.gap-4 { gap: var(--sp-4); } +.gap-5 { gap: var(--sp-5); } + +.text-ellipses { + text-overflow: ellipsis; + overflow-x: clip; + text-wrap: nowrap; +} + +p { + margin: 0; +} + +#music { + position: fixed; + left: 0; + bottom: 0; + width: min-content; + padding: 2em; +} + +.nowplaying { + margin-bottom: var(--sp-3); + text-transform: uppercase; + opacity: .5; + font-weight: bold; +} + +.artwork { + width: 80px; + height: 80px; + border-radius: var(--sp-3); + background: #0008; + box-shadow: + 0 0 var(--sp-5) #000c, + var(--sp-1) var(--sp-1) var(--sp-2) #0004; + transition: transform .5s var(--smooth), opacity .5s, filter .5s; +} + +.title-artist { + max-width: calc(100vw - 2em - 80px); +} + +.title { + padding: 0 var(--sp-5); + font-size: 2em; + font-weight: 800; + transition: transform .5s var(--smooth), opacity .1s; +} + +.artist { + padding: 0 var(--sp-5); + margin-top: calc(0px - var(--sp-3)); + font-size: 1.5em; + font-weight: 600; + transition: transform .5s var(--smooth), opacity .1s; + transition-delay: .05s; +} + +#music.paused, #music.switching { + .artwork { + transform: scale(0.8); + opacity: .5; + filter: grayscale(1); + } + + .title, .artist { + transform: translateX(-16px); + opacity: 0; + } +} diff --git a/music/public/music.js b/music/public/music.js new file mode 100644 index 0000000..115452b --- /dev/null +++ b/music/public/music.js @@ -0,0 +1,48 @@ +const musicEl = document.getElementById("music"); +const artworkEl = document.querySelector(".artwork"); +const titleEl = document.querySelector(".title"); +const artistEl = document.querySelector(".artist"); + +async function sync() { + const eventSource = new EventSource("/music/sse"); + eventSource.addEventListener("open", () => { + console.log("Connected."); + + eventSource.addEventListener("music", event => { + const nextTrack = JSON.parse(event.data) + console.log(`Now playing: ${nextTrack.artist} - ${nextTrack.title}`) + + musicEl.classList.add("switching"); + + setTimeout(() => { + musicEl.classList.remove("switching"); + titleEl.innerText = nextTrack.title; + artistEl.innerText = nextTrack.artist; + artworkEl.src = "/music/artwork?r=" + Math.round(Math.random() * 1_000_000); + }, 500); + }); + + eventSource.addEventListener("music-state", event => { + const data = JSON.parse(event.data) + console.log(data.playing ? "Music playing." : "Music paused."); + if (data.playing) + musicEl.classList.remove("paused"); + else + musicEl.classList.add("paused"); + }); + + const errListener = eventSource.addEventListener("error", () => { + eventSource.removeEventListener("error", errListener); + eventSource.close(); + console.error("Connection lost, or an error has occurred."); + console.log("Attempting to reconnect..."); + + // TODO: stop reconnecting after 10 failed attempts + + setTimeout(() => { + sync(); + }, 1000); + }); + }); +} +sync();