Compare commits

...

39 commits

Author SHA1 Message Date
Laura Hausmann 08a945c80b
Improve log display 2023-11-05 14:13:04 +01:00
Laura Hausmann 9f595e98a3
Fix charset 2023-06-29 16:50:29 +02:00
Laura Hausmann 32d7e1c9cb
Title fixes 2023-06-01 02:13:59 +02:00
Laura Hausmann ae7966a71d
Title fixes 2023-06-01 01:57:55 +02:00
Laura Hausmann 5ee2a3b3c2
Title fixes 2023-06-01 01:57:21 +02:00
Laura Hausmann a89285feaa
Title fixes 2023-06-01 01:56:42 +02:00
Laura Hausmann 41795c11ea
Make title more descriptive 2023-06-01 01:54:18 +02:00
Laura Hausmann 2522214a97
Move lyrics to new page 2023-05-27 03:45:36 +02:00
Laura Hausmann 2d81029c29
Fix log 2023-05-11 19:36:19 +02:00
Laura Hausmann 8bf7db6b17
Fix log 2023-05-11 19:35:04 +02:00
Laura Hausmann c3c220753f
Hopefully fix encoding issues 2023-04-13 15:28:22 +02:00
Laura Hausmann 65ab7f2941
Add copy buttons 2023-04-07 22:45:43 +02:00
Laura Hausmann b70ad17032
Use HttpUtility.UrlEncode 2023-03-16 00:28:55 +01:00
Laura Hausmann 84f81bfa66
Fix + in artist/album name 2023-03-16 00:25:02 +01:00
Laura Hausmann 13282621ad
Fix css 2022-11-21 02:07:43 +01:00
Laura Hausmann dd18ffdbcd
Fix js 2022-11-21 02:06:43 +01:00
Laura Hausmann c3484f485d
Update logging 2022-11-21 02:04:28 +01:00
Laura Hausmann 480cd8329c
Update logging 2022-11-21 02:03:01 +01:00
Laura Hausmann 5408f1a1eb
Update URL 2022-11-20 22:50:05 +01:00
Laura Hausmann e8a558632c
Code cleanup 2022-11-20 17:55:34 +01:00
Laura Hausmann 54fe97c2c7
Change statistics identiy 2022-11-20 17:48:59 +01:00
Laura Hausmann 33dbbae9a1
Add experimental playback logging support 2022-11-20 16:48:10 +01:00
Laura Hausmann 269f050b1c
Update to net70 2022-11-17 03:43:31 +01:00
Laura Hausmann 5624bbefe9
Update to net60 2021-11-16 14:07:05 +01:00
Laura Hausmann e0728158e5
Code cleanup 2021-07-25 23:35:22 +02:00
Laura Hausmann ed04d034da
Update alphanum sort algoritm 2021-07-25 23:10:31 +02:00
Laura Hausmann e8fbfc3bc9
Update 'LICENSE' 2021-07-20 23:23:06 +02:00
Laura Hausmann 765aeb6c11
Update css 2021-07-08 02:53:18 +02:00
Laura Hausmann b6b4937d03
Add layout w/o js for lyrics display 2021-06-25 15:01:44 +02:00
Laura Hausmann ce4783e49c
fix encoding 2021-06-25 11:35:11 +02:00
Laura Hausmann f8ac13a606
Add local fonts 2021-06-24 19:28:52 +02:00
Laura Hausmann 37604c8b1c
Update css 2021-06-24 19:24:03 +02:00
Laura Hausmann 32e7caa48b
Update css 2021-06-23 21:05:28 +02:00
Laura Hausmann 2c02e78afa
add prev/next track shortcuts 2021-05-31 14:23:09 +02:00
Laura Hausmann d1fd31cfe6
fix search and tab-navigation 2021-05-27 16:56:56 +02:00
Laura Hausmann e09c2545a1
select first element on down arrow press, cleanup 2021-05-27 16:50:00 +02:00
Laura Hausmann 75c86b5fd8
fixed encoding 2021-05-27 16:15:56 +02:00
Laura Hausmann 9035211012
Fix keyevents for macOS 2021-05-07 12:23:30 +02:00
Leah 035c1648c1
more modern js, more keyboard-controls (#5)
webmusic.js: prevent default action for keydown events

webmusic.js: check for playable content

webmusic.js: hide selector when track selected

webmusic.js: ignore keyboard controls if ctrl or alt is pressed

webmusic.js: seeking in track with number keys

webmusic.js: use single quotes everywhere

webmusic.js: use arrow functions everywhere

webmusic.js: use const for audioplayer and variable element in function playSong

webmusic.js: use strict mode

webmusic.js: add classes needed for previous 2 commits

webmusic.js: use classes for buttons

webmusic.js: navigating through files/folders with arrowkeys/enter

webmusic.js: removed debug prints, better player handling

webmusic.js: bind to audioplayer-events just once

webmusic.js: renamed gstate to playerState

webmusic.js: renamed setState to setPlayerState and updateState to updatePlayerState

webmusic.css: replaced spaces with tabs

needed changes on template and css to make previous commit work

webmusic.js: refactor continuous- and repeat-button handling

webmusic.js: general code improvements

webmusic.js: rename variable 'sound' to 'audioPlayer'

remove howler.core.js

webmusic.js: replace howler with the native Audio() element

webmusic.js: set onclick-event on state element just once

webmusic.js: skip folders

needed changes on template to make previous commit work

webmusic.js: navigate through files more smartly

webmusic.js: code improovements

webmusic.js: replace spaces with tabs

webmusic.js: replace ifs with switch

Co-authored-by: Leah (ctucx) <leah@ctu.cx>
Co-authored-by: Isabelle <hi@f2k1.de>
Co-authored-by: ctucx <c@ctu.cx>
Reviewed-on: https://git.zotan.services/zotan/webmusic/pulls/5
Co-Authored-By: Leah <leah@ctu.cx>
Co-Committed-By: Leah <leah@ctu.cx>
2021-03-21 18:04:15 +01:00
21 changed files with 610 additions and 437 deletions

2
.gitignore vendored
View file

@ -226,3 +226,5 @@ project.lock.json
music/
webmusic.linux.run
dotnetwarp_temp
.bearer_token

View file

@ -1,4 +1,4 @@
MIT License
Be Gay, Do Crimes License
Copyright (c) 2019 Laura Hausmann
@ -9,6 +9,9 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Be Gay
Do Crimes
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

View file

@ -1,50 +1,107 @@
@page
@using System.IO
@using System.Web
@model IndexModel
@{
ViewData["Title"] = $"webmusic on .NET {Environment.Version}";
if (Model.LogConditionsMet)
ViewData["Title"] = $"{Model.LogDisplay} ~ webmusic on .NET {Environment.Version}";
else
ViewData["Title"] = $"{Model.Displaypath} ~ webmusic on .NET {Environment.Version}";
}
@if (Model.Path.Contains("/..")) {
return;
}
@if (Model.Path.EndsWith(".lrc")) {
Layout = "Shared/_LayoutNojs";
<h3>@Model.Path</h3>
<p>@Html.Raw((await System.IO.File.ReadAllTextAsync("music" + Model.Path)).Replace("\n", "<br/>"))</p>
}
else {
<h2>
@Model.Displaypath <span id="state"></span> <span id="flags"></span>
@Model.Displaypath <span id="state"></span> <span id="flags">[<span id="repeatButton">R</span><span id="continuousButton">C</span>]</span>
</h2>
<a class="action-muted">[..]</a>
<a href="?@Model.PathOneup" id="back" class="entry-muted cfont"> Go back</a>
<a href="?@IndexModel.Encode(Model.PathOneup)" id="back" class="entry-muted cfont"> Go back</a>
<br/>
<a class="action-muted">[--]</a>
<a href="/playlist/@(Request.QueryString).m3u" class="entry-muted cfont"> Download playlist</a>
@if (Model.LogConditionsMet) {
<br/>
<a class="action-muted">[--]</a>
<span id="log" onclick="log_playback('/log?artist=@Model.LogArtist&album=@Model.LogAlbum&url=@Model.LogUrl')" class="entry-muted cfont" style="cursor: pointer">Log playback</span>
<span class="action-muted">/</span>
<span id="copy_hash_np" onclick="copy_hash_np()" class="entry-muted cfont" style="cursor: pointer">Copy #np</span>
<span class="action-muted">/</span>
<span id="copy_journal_full" onclick="copy_journal_full()" class="entry-muted cfont" style="cursor: pointer">Copy journal (full)</span>
<span class="action-muted">/</span>
<span id="copy_journal_album" onclick="copy_journal_album()" class="entry-muted cfont" style="cursor: pointer">Copy journal (album only)</span>
}
<br/>
<br/>
@foreach (var dir in Model.Dirs) {
<li>
<a class="action" href="#">[--]</a>
<a href="?@Model.Path/@dir"> @dir</a>
<a class="action" href="#">[--]</a>
<a class="dir" href="?@IndexModel.Encode(Model.Path + "/" + dir)"> @dir</a>
</li>
}
@foreach (var file in Model.Files) {
var jspath = IndexModel.Encode(Model.Fullpath);
var jsfile = jspath + "/" + IndexModel.Encode(file);
var basename = System.IO.Path.GetFileNameWithoutExtension(file);
var lrcfile = basename + ".lrc";
var lrcpath = System.IO.Path.Combine(Model.Fullpath, lrcfile);
<li>
<script>queue.push('@Html.Raw(jsfile)')</script>
<a class="action" href="@Html.Raw(IndexModel.Encode(Model.Fullpath + "/" + file))" download>[DL]</a>
@if (System.IO.File.Exists(lrcpath)) {
<a class="action" href="?@Model.Path/@lrcfile" target="_blank">[LRC]</a>
}
else if(Directory.GetFiles(@Model.Fullpath, "*.lrc").Length != 0) {
<a class="action">[---]</a>
}
<a href="#" onclick="playSong('@Html.Raw(jsfile)')"> @file</a>
<a class="action" href="@Html.Raw(IndexModel.Encode(Model.Fullpath + "/" + file))" download>[DL]</a>
@if (System.IO.File.Exists(lrcpath)) {
<a class="action" href="/lyrics/?@Html.Raw(IndexModel.Encode(Model.Path + "/" + lrcfile))" target="_blank">[LRC]</a>
}
else if (Directory.GetFiles(Model.Fullpath, "*.lrc").Length != 0) {
<a class="action">[---]</a>
}
<a class="file" href="@Html.Raw(IndexModel.Encode(Model.Fullpath + "/" + file))"> @file</a>
</li>
}
}
<script>
function log_playback(url) {
fetch(url).then(function(response) {
return response.text();
}).then(function(text) {
document.getElementById('log').innerText = 'Log playback (' + text + ')';
});
}
function reset_copy_labels() {
document.getElementById('copy_hash_np').innerText = 'Copy #np';
document.getElementById('copy_journal_full').innerText = 'Copy journal (full)';
document.getElementById('copy_journal_album').innerText = 'Copy journal (album only)';
}
function copy_hash_np() {
copyToClipboard('[#np](@Html.Raw(Log.NowPlayingUrl)) @Html.Raw(Model.CopyArtist) - @Html.Raw(Model.CopyAlbum)');
reset_copy_labels();
document.getElementById('copy_hash_np').innerText = 'Copy #np (Copied!)';
}
function copy_journal_full() {
copyToClipboard('[@Html.Raw(Model.CopyArtist)](' + getUrl('@Html.Raw(Model.LogArtist)') + ') - [@Html.Raw(Model.CopyAlbum)](' + getUrl('@Html.Raw(Model.LogArtist)', '@Html.Raw(Model.LogAlbum)') + ')')
reset_copy_labels();
document.getElementById('copy_journal_full').innerText = 'Copy journal (full) (Copied!)';
}
function copy_journal_album() {
copyToClipboard(', [@Html.Raw(Model.CopyAlbum)](' + getUrl('@Html.Raw(Model.LogArtist)', '@Html.Raw(Model.LogAlbum)') + ')')
reset_copy_labels();
document.getElementById('copy_journal_album').innerText = 'Copy journal (album only) (Copied!)';
}
function getUrl(artist, album) {
let url = location.protocol + '//' + location.host + '/?/' + artist;
if (album) {
url += '/' + album;
}
return url;
}
</script>

View file

@ -4,17 +4,25 @@ using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Web;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace webmusic.Pages {
public class IndexModel : PageModel {
private const string Root = "music";
public List<string> Dirs = new();
public string Displaypath = "";
public List<string> Files = new();
public string Fullpath = "";
public string Path = "";
public string PathOneup = "";
private const string Root = "music";
public List<string> Dirs = new();
public string Displaypath = "";
public List<string> Files = new();
public string Fullpath = "";
public string Path = "";
public string PathOneup = "";
public bool LogConditionsMet;
public string LogArtist;
public string LogAlbum;
public string LogDisplay;
public string LogUrl;
public string CopyArtist;
public string CopyAlbum;
public void OnGet() {
if (Request.QueryString.HasValue)
@ -24,9 +32,9 @@ namespace webmusic.Pages {
Response.Redirect("/Error");
return;
}
if (Path.EndsWith(".m3u"))
Path = Path.Substring(0, Path.Length - 4);
Path = Path[..^4];
if (Path.EndsWith(".lrc"))
return;
@ -41,85 +49,79 @@ namespace webmusic.Pages {
Files.RemoveAll(p => p.EndsWith(".m3u"));
Files.RemoveAll(p => p.EndsWith(".lrc"));
Files.RemoveAll(p => p.StartsWith("."));
Files.Sort(new AlphanumComparatorFast());
Files.Sort(new NaturalSortComparer());
if (Log.StatisticsEnabled && Request.Headers["Remote-User"].Equals(Log.StatisticsUser) && Files.Any()) {
var pathparts = Displaypath.Split(System.IO.Path.DirectorySeparatorChar);
if (pathparts.Length > 2) {
CopyAlbum = pathparts[Index.FromEnd(1)].Replace("'", "\\'");
CopyArtist = pathparts[Index.FromEnd(2)].Replace("'", "\\'");;
LogAlbum = HttpUtility.UrlPathEncode(pathparts[Index.FromEnd(1)]).Replace("'", "\\'").Replace("&", "%26");
LogArtist = HttpUtility.UrlPathEncode(pathparts[Index.FromEnd(2)]).Replace("'", "\\'").Replace("&", "%26");
LogDisplay = $"{pathparts[Index.FromEnd(2)]} - {pathparts[Index.FromEnd(1)]}";
LogUrl = Request.GetEncodedUrl().Replace("'", "\\'").Replace("&", "%26");
LogConditionsMet = true;
}
}
}
public static string Encode(string str) => str.Replace("\"", "%22")
.Replace("'", "%27")
.Replace("?", "%3F")
.Replace("&", "%26")
.Replace(" ", "%20");
public static string Encode(string str) => str.Replace("\"", "%22").Replace("'", "%27").Replace("?", "%3F").Replace("&", "%26").Replace(" ", "%20".Replace("+", "%2B"));
private class AlphanumComparatorFast : IComparer<string> {
public int Compare(string x, string y) {
var s1 = x;
if (s1 == null)
private class NaturalSortComparer : IComparer<string>, IDisposable {
private readonly bool _isAscending;
public NaturalSortComparer(bool inAscendingOrder = true) {
_isAscending = inAscendingOrder;
}
int IComparer<string>.Compare(string x, string y) {
if (x == y)
return 0;
if (!(y is { } s2))
return 0;
var len1 = s1.Length;
var len2 = s2.Length;
var marker1 = 0;
var marker2 = 0;
// Walk through two the strings with two markers.
while (marker1 < len1 && marker2 < len2) {
var ch1 = s1[marker1];
var ch2 = s2[marker2];
// Some buffers we can build up characters in for each chunk.
var space1 = new char[len1];
var loc1 = 0;
var space2 = new char[len2];
var loc2 = 0;
// Walk through all following characters that are digits or
// characters in BOTH strings starting at the appropriate marker.
// Collect char arrays.
do {
space1[loc1++] = ch1;
marker1++;
if (marker1 < len1)
ch1 = s1[marker1];
else
break;
} while (char.IsDigit(ch1) == char.IsDigit(space1[0]));
do {
space2[loc2++] = ch2;
marker2++;
if (marker2 < len2)
ch2 = s2[marker2];
else
break;
} while (char.IsDigit(ch2) == char.IsDigit(space2[0]));
// If we have collected numbers, compare them numerically.
// Otherwise, if we have strings, compare them alphabetically.
var str1 = new string(space1);
var str2 = new string(space2);
int result;
if (char.IsDigit(space1[0]) && char.IsDigit(space2[0])) {
var thisNumericChunk = int.Parse(str1);
var thatNumericChunk = int.Parse(str2);
result = thisNumericChunk.CompareTo(thatNumericChunk);
}
else {
result = string.Compare(str1, str2, StringComparison.Ordinal);
}
if (result != 0)
return result;
if (!_table.TryGetValue(x!, out var x1)) {
x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
_table.Add(x, x1);
}
return len1 - len2;
if (!_table.TryGetValue(y!, out var y1)) {
y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
_table.Add(y, y1);
}
int returnVal;
for (var i = 0; i < x1.Length && i < y1.Length; i++) {
if (x1[i] != y1[i]) {
returnVal = PartCompare(x1[i], y1[i]);
return _isAscending ? returnVal : -returnVal;
}
}
if (y1.Length > x1.Length) {
returnVal = 1;
}
else if (x1.Length > y1.Length) {
returnVal = -1;
}
else {
returnVal = 0;
}
return _isAscending ? returnVal : -returnVal;
}
private static int PartCompare(string left, string right) {
if (!int.TryParse(left, out var x))
return string.Compare(left, right, StringComparison.Ordinal);
return !int.TryParse(right, out var y) ? string.Compare(left, right, StringComparison.Ordinal) : x.CompareTo(y);
}
private Dictionary<string, string[]> _table = new();
public void Dispose() {
_table.Clear();
_table = null;
}
}
}
}
}

54
Pages/Log.cs Normal file
View file

@ -0,0 +1,54 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
namespace webmusic.Pages;
[ApiController, Route("/log")]
public class Log : Controller {
public const bool StatisticsEnabled = true;
public const string StatisticsUser = "zotan";
public const string NowPlayingUrl = "https://zotan.pw/np";
private const string StatisticsUrl = "https://zotan.pw/np/log";
private const string StatisticsSource = "music.ztn.sh";
private readonly string _statisticsToken = System.IO.File.Exists(".bearer_token") ? System.IO.File.ReadAllLines(".bearer_token")[0] : "";
[HttpGet]
public string Get(string artist, string album, string url) {
if (StatisticsEnabled && Request.Headers["Remote-User"].Equals(StatisticsUser)) {
var res = MakeRestRequest(new LogPlaybackRequest { Artist = artist, Title = album, Link = url, Source = StatisticsSource });
Response.StatusCode = (int)res;
return res switch {
HttpStatusCode.Created => "Logged",
HttpStatusCode.Accepted => "Skipped",
HttpStatusCode.Forbidden => "Invalid token",
_ => $"Error {Response.StatusCode} \"{res}\" occured"
};
}
Response.StatusCode = 403;
return null;
}
private HttpStatusCode MakeRestRequest(LogPlaybackRequest rq) {
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(_statisticsToken);
var json = JsonSerializer.Serialize(rq);
var data = new StringContent(json, Encoding.UTF8, "application/json");
var response = client.PostAsync(StatisticsUrl, data).Result;
return response.StatusCode;
}
private class LogPlaybackRequest {
public string Artist { get; set; }
public string Title { get; set; }
public string Source { get; set; }
public string Link { get; set; }
}
}

View file

@ -6,9 +6,6 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>@ViewData["Title"]</title>
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Ubuntu+Mono" rel="stylesheet">
<script src="/howler.core.js"></script>
<script src="/webmusic.js"></script>
<link href="/webmusic.css" rel="stylesheet"/>
<link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png">

View file

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<title>@ViewData["Title"]</title>
<link href="/webmusic.css" rel="stylesheet"/>
<link rel="apple-touch-icon" sizes="57x57" href="/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/favicon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
<link rel="manifest" href="/manifest.json">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="/favicon/ms-icon-144x144.png">
<meta name="theme-color" content="#ffffff">
</head>
<body>
<div id="container">
@RenderBody()
</div>
</body>
</html>

18
Pages/lyrics.cshtml Normal file
View file

@ -0,0 +1,18 @@
@page
@using System.IO
@model IndexModel
@{
ViewData["Title"] = $"{Model.Path.Split("/").Last()} ~ webmusic on .NET {Environment.Version}";
}
@if (Model.Path.Contains("/..")) {
return;
}
@if (!Model.Path.EndsWith(".lrc")) {
Response.Redirect("/" + Request.QueryString);
return;
}
@{
Layout = "Shared/_LayoutNojs";
}
<h3>@Model.Path</h3>
<p>@Html.Raw((await System.IO.File.ReadAllTextAsync("music" + Model.Path)).Replace("\n", "<br/>"))</p>

View file

@ -2,7 +2,7 @@
@model IndexModel
@{
Layout = null;
Response.ContentType = "text/plain";
Response.ContentType = "text/plain; charset=utf-8";
}
@if (Model.Path.Contains("..")) {
return;
@ -12,4 +12,4 @@
}
@foreach (var file in Model.Files) {
@Html.Raw(file + "\n")
}
}

View file

@ -4,11 +4,11 @@
@{
var path = HttpUtility.UrlDecode(Request.QueryString.Value.TrimStart('?'));
Layout = null;
Response.ContentType = "text/plain";
Response.ContentType = "text/plain; charset=utf-8";
}
@if (Model.Path.Contains("..")) {
return;
}
@foreach (var file in Model.Files) {
@Html.Raw("https://" + Request.Host + "/" + Model.Fullpath + "/" + file.Replace("?", "%3F") + "\n")
}
}

View file

@ -1,20 +1,12 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:63053",
"sslPort": 44395
}
},
"profiles": {
"webmusic": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"applicationUrl": "http://localhost:5004",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
}

View file

@ -1,2 +1,4 @@
## webmusic
This project allows users to browse and play music in a minimal web interface.
This project allows users to browse and play music in a minimal web interface.
Contains experimental support for collecting listening statistics in conjunction with [zotan.pw-web](https://git.ztn.sh/zotan/zotan.pw-web).

View file

@ -23,10 +23,6 @@ namespace webmusic {
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public static void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
var provider = new FileExtensionContentTypeProvider();
provider.Mappings[".flac"] = "audio/flac";
provider.Mappings[".m3u"] = "audio/m3u";
provider.Mappings[".opus"] = "audio/opus";
if (env.IsDevelopment()) {
app.UseDeveloperExceptionPage();
}
@ -36,10 +32,11 @@ namespace webmusic {
app.UseHsts();
}
app.UseStaticFiles(new StaticFileOptions {ContentTypeProvider = provider});
var provider = new FileExtensionContentTypeProvider { Mappings = { [".flac"] = "audio/flac", [".m3u"] = "audio/m3u", [".opus"] = "audio/opus" } };
app.UseStaticFiles(new StaticFileOptions { ContentTypeProvider = provider });
app.UseCookiePolicy();
app.UseMvc();
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,162 +0,0 @@
/*!
* howler.js v2.1.2
* howlerjs.com
*
* (c) 2013-2019, James Simpson of GoldFire Studios
* goldfirestudios.com
*
* MIT License
*/(function(){'use strict';var HowlerGlobal=function(){this.init();};HowlerGlobal.prototype={init:function(){var self=this||Howler;self._counter=1000;self._html5AudioPool=[];self.html5PoolSize=10;self._codecs={};self._howls=[];self._muted=false;self._volume=1;self._canPlayEvent='canplaythrough';self._navigator=(typeof window!=='undefined'&&window.navigator)?window.navigator:null;self.masterGain=null;self.noAudio=false;self.usingWebAudio=true;self.autoSuspend=true;self.ctx=null;self.autoUnlock=true;self._setup();return self;},volume:function(vol){var self=this||Howler;vol=parseFloat(vol);if(!self.ctx){setupAudioContext();}
if(typeof vol!=='undefined'&&vol>=0&&vol<=1){self._volume=vol;if(self._muted){return self;}
if(self.usingWebAudio){self.masterGain.gain.setValueAtTime(vol,Howler.ctx.currentTime);}
for(var i=0;i<self._howls.length;i++){if(!self._howls[i]._webAudio){var ids=self._howls[i]._getSoundIds();for(var j=0;j<ids.length;j++){var sound=self._howls[i]._soundById(ids[j]);if(sound&&sound._node){sound._node.volume=sound._volume*vol;}}}}
return self;}
return self._volume;},mute:function(muted){var self=this||Howler;if(!self.ctx){setupAudioContext();}
self._muted=muted;if(self.usingWebAudio){self.masterGain.gain.setValueAtTime(muted?0:self._volume,Howler.ctx.currentTime);}
for(var i=0;i<self._howls.length;i++){if(!self._howls[i]._webAudio){var ids=self._howls[i]._getSoundIds();for(var j=0;j<ids.length;j++){var sound=self._howls[i]._soundById(ids[j]);if(sound&&sound._node){sound._node.muted=(muted)?true:sound._muted;}}}}
return self;},unload:function(){var self=this||Howler;for(var i=self._howls.length-1;i>=0;i--){self._howls[i].unload();}
if(self.usingWebAudio&&self.ctx&&typeof self.ctx.close!=='undefined'){self.ctx.close();self.ctx=null;setupAudioContext();}
return self;},codecs:function(ext){return(this||Howler)._codecs[ext.replace(/^x-/,'')];},_setup:function(){var self=this||Howler;self.state=self.ctx?self.ctx.state||'suspended':'suspended';self._autoSuspend();if(!self.usingWebAudio){if(typeof Audio!=='undefined'){try{var test=new Audio();if(typeof test.oncanplaythrough==='undefined'){self._canPlayEvent='canplay';}}catch(e){self.noAudio=true;}}else{self.noAudio=true;}}
try{var test=new Audio();if(test.muted){self.noAudio=true;}}catch(e){}
if(!self.noAudio){self._setupCodecs();}
return self;},_setupCodecs:function(){var self=this||Howler;var audioTest=null;try{audioTest=(typeof Audio!=='undefined')?new Audio():null;}catch(err){return self;}
if(!audioTest||typeof audioTest.canPlayType!=='function'){return self;}
var mpegTest=audioTest.canPlayType('audio/mpeg;').replace(/^no$/,'');var checkOpera=self._navigator&&self._navigator.userAgent.match(/OPR\/([0-6].)/g);var isOldOpera=(checkOpera&&parseInt(checkOpera[0].split('/')[1],10)<33);self._codecs={mp3:!!(!isOldOpera&&(mpegTest||audioTest.canPlayType('audio/mp3;').replace(/^no$/,''))),mpeg:!!mpegTest,opus:!!audioTest.canPlayType('audio/ogg; codecs="opus"').replace(/^no$/,''),ogg:!!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''),oga:!!audioTest.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,''),wav:!!audioTest.canPlayType('audio/wav; codecs="1"').replace(/^no$/,''),aac:!!audioTest.canPlayType('audio/aac;').replace(/^no$/,''),caf:!!audioTest.canPlayType('audio/x-caf;').replace(/^no$/,''),m4a:!!(audioTest.canPlayType('audio/x-m4a;')||audioTest.canPlayType('audio/m4a;')||audioTest.canPlayType('audio/aac;')).replace(/^no$/,''),mp4:!!(audioTest.canPlayType('audio/x-mp4;')||audioTest.canPlayType('audio/mp4;')||audioTest.canPlayType('audio/aac;')).replace(/^no$/,''),weba:!!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,''),webm:!!audioTest.canPlayType('audio/webm; codecs="vorbis"').replace(/^no$/,''),dolby:!!audioTest.canPlayType('audio/mp4; codecs="ec-3"').replace(/^no$/,''),flac:!!(audioTest.canPlayType('audio/x-flac;')||audioTest.canPlayType('audio/flac;')).replace(/^no$/,'')};return self;},_unlockAudio:function(){var self=this||Howler;if(self._audioUnlocked||!self.ctx){return;}
self._audioUnlocked=false;self.autoUnlock=false;if(!self._mobileUnloaded&&self.ctx.sampleRate!==44100){self._mobileUnloaded=true;self.unload();}
self._scratchBuffer=self.ctx.createBuffer(1,1,22050);var unlock=function(e){for(var i=0;i<self.html5PoolSize;i++){try{var audioNode=new Audio();audioNode._unlocked=true;self._releaseHtml5Audio(audioNode);}catch(e){self.noAudio=true;}}
for(var i=0;i<self._howls.length;i++){if(!self._howls[i]._webAudio){var ids=self._howls[i]._getSoundIds();for(var j=0;j<ids.length;j++){var sound=self._howls[i]._soundById(ids[j]);if(sound&&sound._node&&!sound._node._unlocked){sound._node._unlocked=true;sound._node.load();}}}}
self._autoResume();var source=self.ctx.createBufferSource();source.buffer=self._scratchBuffer;source.connect(self.ctx.destination);if(typeof source.start==='undefined'){source.noteOn(0);}else{source.start(0);}
if(typeof self.ctx.resume==='function'){self.ctx.resume();}
source.onended=function(){source.disconnect(0);self._audioUnlocked=true;document.removeEventListener('touchstart',unlock,true);document.removeEventListener('touchend',unlock,true);document.removeEventListener('click',unlock,true);for(var i=0;i<self._howls.length;i++){self._howls[i]._emit('unlock');}};};document.addEventListener('touchstart',unlock,true);document.addEventListener('touchend',unlock,true);document.addEventListener('click',unlock,true);return self;},_obtainHtml5Audio:function(){var self=this||Howler;if(self._html5AudioPool.length){return self._html5AudioPool.pop();}
var testPlay=new Audio().play();if(testPlay&&typeof Promise!=='undefined'&&(testPlay instanceof Promise||typeof testPlay.then==='function')){testPlay.catch(function(){console.warn('HTML5 Audio pool exhausted, returning potentially locked audio object.');});}
return new Audio();},_releaseHtml5Audio:function(audio){var self=this||Howler;if(audio._unlocked){self._html5AudioPool.push(audio);}
return self;},_autoSuspend:function(){var self=this;if(!self.autoSuspend||!self.ctx||typeof self.ctx.suspend==='undefined'||!Howler.usingWebAudio){return;}
for(var i=0;i<self._howls.length;i++){if(self._howls[i]._webAudio){for(var j=0;j<self._howls[i]._sounds.length;j++){if(!self._howls[i]._sounds[j]._paused){return self;}}}}
if(self._suspendTimer){clearTimeout(self._suspendTimer);}
self._suspendTimer=setTimeout(function(){if(!self.autoSuspend){return;}
self._suspendTimer=null;self.state='suspending';self.ctx.suspend().then(function(){self.state='suspended';if(self._resumeAfterSuspend){delete self._resumeAfterSuspend;self._autoResume();}});},30000);return self;},_autoResume:function(){var self=this;if(!self.ctx||typeof self.ctx.resume==='undefined'||!Howler.usingWebAudio){return;}
if(self.state==='running'&&self._suspendTimer){clearTimeout(self._suspendTimer);self._suspendTimer=null;}else if(self.state==='suspended'){self.ctx.resume().then(function(){self.state='running';for(var i=0;i<self._howls.length;i++){self._howls[i]._emit('resume');}});if(self._suspendTimer){clearTimeout(self._suspendTimer);self._suspendTimer=null;}}else if(self.state==='suspending'){self._resumeAfterSuspend=true;}
return self;}};var Howler=new HowlerGlobal();var Howl=function(o){var self=this;if(!o.src||o.src.length===0){console.error('An array of source files must be passed with any new Howl.');return;}
self.init(o);};Howl.prototype={init:function(o){var self=this;if(!Howler.ctx){setupAudioContext();}
self._autoplay=o.autoplay||false;self._format=(typeof o.format!=='string')?o.format:[o.format];self._html5=o.html5||false;self._muted=o.mute||false;self._loop=o.loop||false;self._pool=o.pool||5;self._preload=(typeof o.preload==='boolean')?o.preload:true;self._rate=o.rate||1;self._sprite=o.sprite||{};self._src=(typeof o.src!=='string')?o.src:[o.src];self._volume=o.volume!==undefined?o.volume:1;self._xhrWithCredentials=o.xhrWithCredentials||false;self._duration=0;self._state='unloaded';self._sounds=[];self._endTimers={};self._queue=[];self._playLock=false;self._onend=o.onend?[{fn:o.onend}]:[];self._onfade=o.onfade?[{fn:o.onfade}]:[];self._onload=o.onload?[{fn:o.onload}]:[];self._onloaderror=o.onloaderror?[{fn:o.onloaderror}]:[];self._onplayerror=o.onplayerror?[{fn:o.onplayerror}]:[];self._onpause=o.onpause?[{fn:o.onpause}]:[];self._onplay=o.onplay?[{fn:o.onplay}]:[];self._onstop=o.onstop?[{fn:o.onstop}]:[];self._onmute=o.onmute?[{fn:o.onmute}]:[];self._onvolume=o.onvolume?[{fn:o.onvolume}]:[];self._onrate=o.onrate?[{fn:o.onrate}]:[];self._onseek=o.onseek?[{fn:o.onseek}]:[];self._onunlock=o.onunlock?[{fn:o.onunlock}]:[];self._onresume=[];self._webAudio=Howler.usingWebAudio&&!self._html5;if(typeof Howler.ctx!=='undefined'&&Howler.ctx&&Howler.autoUnlock){Howler._unlockAudio();}
Howler._howls.push(self);if(self._autoplay){self._queue.push({event:'play',action:function(){self.play();}});}
if(self._preload){self.load();}
return self;},load:function(){var self=this;var url=null;if(Howler.noAudio){self._emit('loaderror',null,'No audio support.');return;}
if(typeof self._src==='string'){self._src=[self._src];}
for(var i=0;i<self._src.length;i++){var ext,str;if(self._format&&self._format[i]){ext=self._format[i];}else{str=self._src[i];if(typeof str!=='string'){self._emit('loaderror',null,'Non-string found in selected audio sources - ignoring.');continue;}
ext=/^data:audio\/([^;,]+);/i.exec(str);if(!ext){ext=/\.([^.]+)$/.exec(str.split('?',1)[0]);}
if(ext){ext=ext[1].toLowerCase();}}
if(!ext){console.warn('No file extension was found. Consider using the "format" property or specify an extension.');}
if(ext&&Howler.codecs(ext)){url=self._src[i];break;}}
if(!url){self._emit('loaderror',null,'No codec support for selected audio sources.');return;}
self._src=url;self._state='loading';if(window.location.protocol==='https:'&&url.slice(0,5)==='http:'){self._html5=true;self._webAudio=false;}
new Sound(self);if(self._webAudio){loadBuffer(self);}
return self;},play:function(sprite,internal){var self=this;var id=null;if(typeof sprite==='number'){id=sprite;sprite=null;}else if(typeof sprite==='string'&&self._state==='loaded'&&!self._sprite[sprite]){return null;}else if(typeof sprite==='undefined'){sprite='__default';if(!self._playLock){var num=0;for(var i=0;i<self._sounds.length;i++){if(self._sounds[i]._paused&&!self._sounds[i]._ended){num++;id=self._sounds[i]._id;}}
if(num===1){sprite=null;}else{id=null;}}}
var sound=id?self._soundById(id):self._inactiveSound();if(!sound){return null;}
if(id&&!sprite){sprite=sound._sprite||'__default';}
if(self._state!=='loaded'){sound._sprite=sprite;sound._ended=false;var soundId=sound._id;self._queue.push({event:'play',action:function(){self.play(soundId);}});return soundId;}
if(id&&!sound._paused){if(!internal){self._loadQueue('play');}
return sound._id;}
if(self._webAudio){Howler._autoResume();}
var seek=Math.max(0,sound._seek>0?sound._seek:self._sprite[sprite][0]/1000);var duration=Math.max(0,((self._sprite[sprite][0]+self._sprite[sprite][1])/1000)-seek);var timeout=(duration*1000)/Math.abs(sound._rate);var start=self._sprite[sprite][0]/1000;var stop=(self._sprite[sprite][0]+self._sprite[sprite][1])/1000;var loop=!!(sound._loop||self._sprite[sprite][2]);sound._sprite=sprite;sound._ended=false;var setParams=function(){sound._paused=false;sound._seek=seek;sound._start=start;sound._stop=stop;sound._loop=loop;};if(seek>=stop){self._ended(sound);return;}
var node=sound._node;if(self._webAudio){var playWebAudio=function(){self._playLock=false;setParams();self._refreshBuffer(sound);var vol=(sound._muted||self._muted)?0:sound._volume;node.gain.setValueAtTime(vol,Howler.ctx.currentTime);sound._playStart=Howler.ctx.currentTime;if(typeof node.bufferSource.start==='undefined'){sound._loop?node.bufferSource.noteGrainOn(0,seek,86400):node.bufferSource.noteGrainOn(0,seek,duration);}else{sound._loop?node.bufferSource.start(0,seek,86400):node.bufferSource.start(0,seek,duration);}
if(timeout!==Infinity){self._endTimers[sound._id]=setTimeout(self._ended.bind(self,sound),timeout);}
if(!internal){setTimeout(function(){self._emit('play',sound._id);self._loadQueue();},0);}};if(Howler.state==='running'){playWebAudio();}else{self._playLock=true;self.once('resume',playWebAudio);self._clearTimer(sound._id);}}else{var playHtml5=function(){node.currentTime=seek;node.muted=sound._muted||self._muted||Howler._muted||node.muted;node.volume=sound._volume*Howler.volume();node.playbackRate=sound._rate;try{var play=node.play();if(play&&typeof Promise!=='undefined'&&(play instanceof Promise||typeof play.then==='function')){self._playLock=true;setParams();play.then(function(){self._playLock=false;node._unlocked=true;if(!internal){self._emit('play',sound._id);self._loadQueue();}}).catch(function(){self._playLock=false;self._emit('playerror',sound._id,'Playback was unable to start. This is most commonly an issue '+
'on mobile devices and Chrome where playback was not within a user interaction.');sound._ended=true;sound._paused=true;});}else if(!internal){self._playLock=false;setParams();self._emit('play',sound._id);self._loadQueue();}
node.playbackRate=sound._rate;if(node.paused){self._emit('playerror',sound._id,'Playback was unable to start. This is most commonly an issue '+
'on mobile devices and Chrome where playback was not within a user interaction.');return;}
if(sprite!=='__default'||sound._loop){self._endTimers[sound._id]=setTimeout(self._ended.bind(self,sound),timeout);}else{self._endTimers[sound._id]=function(){self._ended(sound);node.removeEventListener('ended',self._endTimers[sound._id],false);};node.addEventListener('ended',self._endTimers[sound._id],false);}}catch(err){self._emit('playerror',sound._id,err);}};if(node.src==='data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA'){node.src=self._src;node.load();}
var loadedNoReadyState=(window&&window.ejecta)||(!node.readyState&&Howler._navigator.isCocoonJS);if(node.readyState>=3||loadedNoReadyState){playHtml5();}else{self._playLock=true;var listener=function(){playHtml5();node.removeEventListener(Howler._canPlayEvent,listener,false);};node.addEventListener(Howler._canPlayEvent,listener,false);self._clearTimer(sound._id);}}
return sound._id;},pause:function(id){var self=this;if(self._state!=='loaded'||self._playLock){self._queue.push({event:'pause',action:function(){self.pause(id);}});return self;}
var ids=self._getSoundIds(id);for(var i=0;i<ids.length;i++){self._clearTimer(ids[i]);var sound=self._soundById(ids[i]);if(sound&&!sound._paused){sound._seek=self.seek(ids[i]);sound._rateSeek=0;sound._paused=true;self._stopFade(ids[i]);if(sound._node){if(self._webAudio){if(!sound._node.bufferSource){continue;}
if(typeof sound._node.bufferSource.stop==='undefined'){sound._node.bufferSource.noteOff(0);}else{sound._node.bufferSource.stop(0);}
self._cleanBuffer(sound._node);}else if(!isNaN(sound._node.duration)||sound._node.duration===Infinity){sound._node.pause();}}}
if(!arguments[1]){self._emit('pause',sound?sound._id:null);}}
return self;},stop:function(id,internal){var self=this;if(self._state!=='loaded'||self._playLock){self._queue.push({event:'stop',action:function(){self.stop(id);}});return self;}
var ids=self._getSoundIds(id);for(var i=0;i<ids.length;i++){self._clearTimer(ids[i]);var sound=self._soundById(ids[i]);if(sound){sound._seek=sound._start||0;sound._rateSeek=0;sound._paused=true;sound._ended=true;self._stopFade(ids[i]);if(sound._node){if(self._webAudio){if(sound._node.bufferSource){if(typeof sound._node.bufferSource.stop==='undefined'){sound._node.bufferSource.noteOff(0);}else{sound._node.bufferSource.stop(0);}
self._cleanBuffer(sound._node);}}else if(!isNaN(sound._node.duration)||sound._node.duration===Infinity){sound._node.currentTime=sound._start||0;sound._node.pause();if(sound._node.duration===Infinity){self._clearSound(sound._node);}}}
if(!internal){self._emit('stop',sound._id);}}}
return self;},mute:function(muted,id){var self=this;if(self._state!=='loaded'||self._playLock){self._queue.push({event:'mute',action:function(){self.mute(muted,id);}});return self;}
if(typeof id==='undefined'){if(typeof muted==='boolean'){self._muted=muted;}else{return self._muted;}}
var ids=self._getSoundIds(id);for(var i=0;i<ids.length;i++){var sound=self._soundById(ids[i]);if(sound){sound._muted=muted;if(sound._interval){self._stopFade(sound._id);}
if(self._webAudio&&sound._node){sound._node.gain.setValueAtTime(muted?0:sound._volume,Howler.ctx.currentTime);}else if(sound._node){sound._node.muted=Howler._muted?true:muted;}
self._emit('mute',sound._id);}}
return self;},volume:function(){var self=this;var args=arguments;var vol,id;if(args.length===0){return self._volume;}else if(args.length===1||args.length===2&&typeof args[1]==='undefined'){var ids=self._getSoundIds();var index=ids.indexOf(args[0]);if(index>=0){id=parseInt(args[0],10);}else{vol=parseFloat(args[0]);}}else if(args.length>=2){vol=parseFloat(args[0]);id=parseInt(args[1],10);}
var sound;if(typeof vol!=='undefined'&&vol>=0&&vol<=1){if(self._state!=='loaded'||self._playLock){self._queue.push({event:'volume',action:function(){self.volume.apply(self,args);}});return self;}
if(typeof id==='undefined'){self._volume=vol;}
id=self._getSoundIds(id);for(var i=0;i<id.length;i++){sound=self._soundById(id[i]);if(sound){sound._volume=vol;if(!args[2]){self._stopFade(id[i]);}
if(self._webAudio&&sound._node&&!sound._muted){sound._node.gain.setValueAtTime(vol,Howler.ctx.currentTime);}else if(sound._node&&!sound._muted){sound._node.volume=vol*Howler.volume();}
self._emit('volume',sound._id);}}}else{sound=id?self._soundById(id):self._sounds[0];return sound?sound._volume:0;}
return self;},fade:function(from,to,len,id){var self=this;if(self._state!=='loaded'||self._playLock){self._queue.push({event:'fade',action:function(){self.fade(from,to,len,id);}});return self;}
from=parseFloat(from);to=parseFloat(to);len=parseFloat(len);self.volume(from,id);var ids=self._getSoundIds(id);for(var i=0;i<ids.length;i++){var sound=self._soundById(ids[i]);if(sound){if(!id){self._stopFade(ids[i]);}
if(self._webAudio&&!sound._muted){var currentTime=Howler.ctx.currentTime;var end=currentTime+(len/1000);sound._volume=from;sound._node.gain.setValueAtTime(from,currentTime);sound._node.gain.linearRampToValueAtTime(to,end);}
self._startFadeInterval(sound,from,to,len,ids[i],typeof id==='undefined');}}
return self;},_startFadeInterval:function(sound,from,to,len,id,isGroup){var self=this;var vol=from;var diff=to-from;var steps=Math.abs(diff/0.01);var stepLen=Math.max(4,(steps>0)?len/steps:len);var lastTick=Date.now();sound._fadeTo=to;sound._interval=setInterval(function(){var tick=(Date.now()-lastTick)/len;lastTick=Date.now();vol+=diff*tick;vol=Math.max(0,vol);vol=Math.min(1,vol);vol=Math.round(vol*100)/100;if(self._webAudio){sound._volume=vol;}else{self.volume(vol,sound._id,true);}
if(isGroup){self._volume=vol;}
if((to<from&&vol<=to)||(to>from&&vol>=to)){clearInterval(sound._interval);sound._interval=null;sound._fadeTo=null;self.volume(to,sound._id);self._emit('fade',sound._id);}},stepLen);},_stopFade:function(id){var self=this;var sound=self._soundById(id);if(sound&&sound._interval){if(self._webAudio){sound._node.gain.cancelScheduledValues(Howler.ctx.currentTime);}
clearInterval(sound._interval);sound._interval=null;self.volume(sound._fadeTo,id);sound._fadeTo=null;self._emit('fade',id);}
return self;},loop:function(){var self=this;var args=arguments;var loop,id,sound;if(args.length===0){return self._loop;}else if(args.length===1){if(typeof args[0]==='boolean'){loop=args[0];self._loop=loop;}else{sound=self._soundById(parseInt(args[0],10));return sound?sound._loop:false;}}else if(args.length===2){loop=args[0];id=parseInt(args[1],10);}
var ids=self._getSoundIds(id);for(var i=0;i<ids.length;i++){sound=self._soundById(ids[i]);if(sound){sound._loop=loop;if(self._webAudio&&sound._node&&sound._node.bufferSource){sound._node.bufferSource.loop=loop;if(loop){sound._node.bufferSource.loopStart=sound._start||0;sound._node.bufferSource.loopEnd=sound._stop;}}}}
return self;},rate:function(){var self=this;var args=arguments;var rate,id;if(args.length===0){id=self._sounds[0]._id;}else if(args.length===1){var ids=self._getSoundIds();var index=ids.indexOf(args[0]);if(index>=0){id=parseInt(args[0],10);}else{rate=parseFloat(args[0]);}}else if(args.length===2){rate=parseFloat(args[0]);id=parseInt(args[1],10);}
var sound;if(typeof rate==='number'){if(self._state!=='loaded'||self._playLock){self._queue.push({event:'rate',action:function(){self.rate.apply(self,args);}});return self;}
if(typeof id==='undefined'){self._rate=rate;}
id=self._getSoundIds(id);for(var i=0;i<id.length;i++){sound=self._soundById(id[i]);if(sound){if(self.playing(id[i])){sound._rateSeek=self.seek(id[i]);sound._playStart=self._webAudio?Howler.ctx.currentTime:sound._playStart;}
sound._rate=rate;if(self._webAudio&&sound._node&&sound._node.bufferSource){sound._node.bufferSource.playbackRate.setValueAtTime(rate,Howler.ctx.currentTime);}else if(sound._node){sound._node.playbackRate=rate;}
var seek=self.seek(id[i]);var duration=((self._sprite[sound._sprite][0]+self._sprite[sound._sprite][1])/1000)-seek;var timeout=(duration*1000)/Math.abs(sound._rate);if(self._endTimers[id[i]]||!sound._paused){self._clearTimer(id[i]);self._endTimers[id[i]]=setTimeout(self._ended.bind(self,sound),timeout);}
self._emit('rate',sound._id);}}}else{sound=self._soundById(id);return sound?sound._rate:self._rate;}
return self;},seek:function(){var self=this;var args=arguments;var seek,id;if(args.length===0){id=self._sounds[0]._id;}else if(args.length===1){var ids=self._getSoundIds();var index=ids.indexOf(args[0]);if(index>=0){id=parseInt(args[0],10);}else if(self._sounds.length){id=self._sounds[0]._id;seek=parseFloat(args[0]);}}else if(args.length===2){seek=parseFloat(args[0]);id=parseInt(args[1],10);}
if(typeof id==='undefined'){return self;}
if(self._state!=='loaded'||self._playLock){self._queue.push({event:'seek',action:function(){self.seek.apply(self,args);}});return self;}
var sound=self._soundById(id);if(sound){if(typeof seek==='number'&&seek>=0){var playing=self.playing(id);if(playing){self.pause(id,true);}
sound._seek=seek;sound._ended=false;self._clearTimer(id);if(!self._webAudio&&sound._node&&!isNaN(sound._node.duration)){sound._node.currentTime=seek;}
var seekAndEmit=function(){self._emit('seek',id);if(playing){self.play(id,true);}};if(playing&&!self._webAudio){var emitSeek=function(){if(!self._playLock){seekAndEmit();}else{setTimeout(emitSeek,0);}};setTimeout(emitSeek,0);}else{seekAndEmit();}}else{if(self._webAudio){var realTime=self.playing(id)?Howler.ctx.currentTime-sound._playStart:0;var rateSeek=sound._rateSeek?sound._rateSeek-sound._seek:0;return sound._seek+(rateSeek+realTime*Math.abs(sound._rate));}else{return sound._node.currentTime;}}}
return self;},playing:function(id){var self=this;if(typeof id==='number'){var sound=self._soundById(id);return sound?!sound._paused:false;}
for(var i=0;i<self._sounds.length;i++){if(!self._sounds[i]._paused){return true;}}
return false;},duration:function(id){var self=this;var duration=self._duration;var sound=self._soundById(id);if(sound){duration=self._sprite[sound._sprite][1]/1000;}
return duration;},state:function(){return this._state;},unload:function(){var self=this;var sounds=self._sounds;for(var i=0;i<sounds.length;i++){if(!sounds[i]._paused){self.stop(sounds[i]._id);}
if(!self._webAudio){self._clearSound(sounds[i]._node);sounds[i]._node.removeEventListener('error',sounds[i]._errorFn,false);sounds[i]._node.removeEventListener(Howler._canPlayEvent,sounds[i]._loadFn,false);Howler._releaseHtml5Audio(sounds[i]._node);}
delete sounds[i]._node;self._clearTimer(sounds[i]._id);}
var index=Howler._howls.indexOf(self);if(index>=0){Howler._howls.splice(index,1);}
var remCache=true;for(i=0;i<Howler._howls.length;i++){if(Howler._howls[i]._src===self._src||self._src.indexOf(Howler._howls[i]._src)>=0){remCache=false;break;}}
if(cache&&remCache){delete cache[self._src];}
Howler.noAudio=false;self._state='unloaded';self._sounds=[];self=null;return null;},on:function(event,fn,id,once){var self=this;var events=self['_on'+event];if(typeof fn==='function'){events.push(once?{id:id,fn:fn,once:once}:{id:id,fn:fn});}
return self;},off:function(event,fn,id){var self=this;var events=self['_on'+event];var i=0;if(typeof fn==='number'){id=fn;fn=null;}
if(fn||id){for(i=0;i<events.length;i++){var isId=(id===events[i].id);if(fn===events[i].fn&&isId||!fn&&isId){events.splice(i,1);break;}}}else if(event){self['_on'+event]=[];}else{var keys=Object.keys(self);for(i=0;i<keys.length;i++){if((keys[i].indexOf('_on')===0)&&Array.isArray(self[keys[i]])){self[keys[i]]=[];}}}
return self;},once:function(event,fn,id){var self=this;self.on(event,fn,id,1);return self;},_emit:function(event,id,msg){var self=this;var events=self['_on'+event];for(var i=events.length-1;i>=0;i--){if(!events[i].id||events[i].id===id||event==='load'){setTimeout(function(fn){fn.call(this,id,msg);}.bind(self,events[i].fn),0);if(events[i].once){self.off(event,events[i].fn,events[i].id);}}}
self._loadQueue(event);return self;},_loadQueue:function(event){var self=this;if(self._queue.length>0){var task=self._queue[0];if(task.event===event){self._queue.shift();self._loadQueue();}
if(!event){task.action();}}
return self;},_ended:function(sound){var self=this;var sprite=sound._sprite;if(!self._webAudio&&sound._node&&!sound._node.paused&&!sound._node.ended&&sound._node.currentTime<sound._stop){setTimeout(self._ended.bind(self,sound),100);return self;}
var loop=!!(sound._loop||self._sprite[sprite][2]);self._emit('end',sound._id);if(!self._webAudio&&loop){self.stop(sound._id,true).play(sound._id);}
if(self._webAudio&&loop){self._emit('play',sound._id);sound._seek=sound._start||0;sound._rateSeek=0;sound._playStart=Howler.ctx.currentTime;var timeout=((sound._stop-sound._start)*1000)/Math.abs(sound._rate);self._endTimers[sound._id]=setTimeout(self._ended.bind(self,sound),timeout);}
if(self._webAudio&&!loop){sound._paused=true;sound._ended=true;sound._seek=sound._start||0;sound._rateSeek=0;self._clearTimer(sound._id);self._cleanBuffer(sound._node);Howler._autoSuspend();}
if(!self._webAudio&&!loop){self.stop(sound._id,true);}
return self;},_clearTimer:function(id){var self=this;if(self._endTimers[id]){if(typeof self._endTimers[id]!=='function'){clearTimeout(self._endTimers[id]);}else{var sound=self._soundById(id);if(sound&&sound._node){sound._node.removeEventListener('ended',self._endTimers[id],false);}}
delete self._endTimers[id];}
return self;},_soundById:function(id){var self=this;for(var i=0;i<self._sounds.length;i++){if(id===self._sounds[i]._id){return self._sounds[i];}}
return null;},_inactiveSound:function(){var self=this;self._drain();for(var i=0;i<self._sounds.length;i++){if(self._sounds[i]._ended){return self._sounds[i].reset();}}
return new Sound(self);},_drain:function(){var self=this;var limit=self._pool;var cnt=0;var i=0;if(self._sounds.length<limit){return;}
for(i=0;i<self._sounds.length;i++){if(self._sounds[i]._ended){cnt++;}}
for(i=self._sounds.length-1;i>=0;i--){if(cnt<=limit){return;}
if(self._sounds[i]._ended){if(self._webAudio&&self._sounds[i]._node){self._sounds[i]._node.disconnect(0);}
self._sounds.splice(i,1);cnt--;}}},_getSoundIds:function(id){var self=this;if(typeof id==='undefined'){var ids=[];for(var i=0;i<self._sounds.length;i++){ids.push(self._sounds[i]._id);}
return ids;}else{return[id];}},_refreshBuffer:function(sound){var self=this;sound._node.bufferSource=Howler.ctx.createBufferSource();sound._node.bufferSource.buffer=cache[self._src];if(sound._panner){sound._node.bufferSource.connect(sound._panner);}else{sound._node.bufferSource.connect(sound._node);}
sound._node.bufferSource.loop=sound._loop;if(sound._loop){sound._node.bufferSource.loopStart=sound._start||0;sound._node.bufferSource.loopEnd=sound._stop||0;}
sound._node.bufferSource.playbackRate.setValueAtTime(sound._rate,Howler.ctx.currentTime);return self;},_cleanBuffer:function(node){var self=this;var isIOS=Howler._navigator&&Howler._navigator.vendor.indexOf('Apple')>=0;if(Howler._scratchBuffer&&node.bufferSource){node.bufferSource.onended=null;node.bufferSource.disconnect(0);if(isIOS){try{node.bufferSource.buffer=Howler._scratchBuffer;}catch(e){}}}
node.bufferSource=null;return self;},_clearSound:function(node){var checkIE=/MSIE |Trident\//.test(Howler._navigator&&Howler._navigator.userAgent);if(!checkIE){node.src='data:audio/wav;base64,UklGRigAAABXQVZFZm10IBIAAAABAAEARKwAAIhYAQACABAAAABkYXRhAgAAAAEA';}}};var Sound=function(howl){this._parent=howl;this.init();};Sound.prototype={init:function(){var self=this;var parent=self._parent;self._muted=parent._muted;self._loop=parent._loop;self._volume=parent._volume;self._rate=parent._rate;self._seek=0;self._paused=true;self._ended=true;self._sprite='__default';self._id=++Howler._counter;parent._sounds.push(self);self.create();return self;},create:function(){var self=this;var parent=self._parent;var volume=(Howler._muted||self._muted||self._parent._muted)?0:self._volume;if(parent._webAudio){self._node=(typeof Howler.ctx.createGain==='undefined')?Howler.ctx.createGainNode():Howler.ctx.createGain();self._node.gain.setValueAtTime(volume,Howler.ctx.currentTime);self._node.paused=true;self._node.connect(Howler.masterGain);}else{self._node=Howler._obtainHtml5Audio();self._errorFn=self._errorListener.bind(self);self._node.addEventListener('error',self._errorFn,false);self._loadFn=self._loadListener.bind(self);self._node.addEventListener(Howler._canPlayEvent,self._loadFn,false);self._node.src=parent._src;self._node.preload='auto';self._node.volume=volume*Howler.volume();self._node.load();}
return self;},reset:function(){var self=this;var parent=self._parent;self._muted=parent._muted;self._loop=parent._loop;self._volume=parent._volume;self._rate=parent._rate;self._seek=0;self._rateSeek=0;self._paused=true;self._ended=true;self._sprite='__default';self._id=++Howler._counter;return self;},_errorListener:function(){var self=this;self._parent._emit('loaderror',self._id,self._node.error?self._node.error.code:0);self._node.removeEventListener('error',self._errorFn,false);},_loadListener:function(){var self=this;var parent=self._parent;parent._duration=Math.ceil(self._node.duration*10)/10;if(Object.keys(parent._sprite).length===0){parent._sprite={__default:[0,parent._duration*1000]};}
if(parent._state!=='loaded'){parent._state='loaded';parent._emit('load');parent._loadQueue();}
self._node.removeEventListener(Howler._canPlayEvent,self._loadFn,false);}};var cache={};var loadBuffer=function(self){var url=self._src;if(cache[url]){self._duration=cache[url].duration;loadSound(self);return;}
if(/^data:[^;]+;base64,/.test(url)){var data=atob(url.split(',')[1]);var dataView=new Uint8Array(data.length);for(var i=0;i<data.length;++i){dataView[i]=data.charCodeAt(i);}
decodeAudioData(dataView.buffer,self);}else{var xhr=new XMLHttpRequest();xhr.open('GET',url,true);xhr.withCredentials=self._xhrWithCredentials;xhr.responseType='arraybuffer';xhr.onload=function(){var code=(xhr.status+'')[0];if(code!=='0'&&code!=='2'&&code!=='3'){self._emit('loaderror',null,'Failed loading audio file with status: '+xhr.status+'.');return;}
decodeAudioData(xhr.response,self);};xhr.onerror=function(){if(self._webAudio){self._html5=true;self._webAudio=false;self._sounds=[];delete cache[url];self.load();}};safeXhrSend(xhr);}};var safeXhrSend=function(xhr){try{xhr.send();}catch(e){xhr.onerror();}};var decodeAudioData=function(arraybuffer,self){var error=function(){self._emit('loaderror',null,'Decoding audio data failed.');};var success=function(buffer){if(buffer&&self._sounds.length>0){cache[self._src]=buffer;loadSound(self,buffer);}else{error();}};if(typeof Promise!=='undefined'&&Howler.ctx.decodeAudioData.length===1){Howler.ctx.decodeAudioData(arraybuffer).then(success).catch(error);}else{Howler.ctx.decodeAudioData(arraybuffer,success,error);}}
var loadSound=function(self,buffer){if(buffer&&!self._duration){self._duration=buffer.duration;}
if(Object.keys(self._sprite).length===0){self._sprite={__default:[0,self._duration*1000]};}
if(self._state!=='loaded'){self._state='loaded';self._emit('load');self._loadQueue();}};var setupAudioContext=function(){if(!Howler.usingWebAudio){return;}
try{if(typeof AudioContext!=='undefined'){Howler.ctx=new AudioContext();}else if(typeof webkitAudioContext!=='undefined'){Howler.ctx=new webkitAudioContext();}else{Howler.usingWebAudio=false;}}catch(e){Howler.usingWebAudio=false;}
if(!Howler.ctx){Howler.usingWebAudio=false;}
var iOS=(/iP(hone|od|ad)/.test(Howler._navigator&&Howler._navigator.platform));var appVersion=Howler._navigator&&Howler._navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);var version=appVersion?parseInt(appVersion[1],10):null;if(iOS&&version&&version<9){var safari=/safari/.test(Howler._navigator&&Howler._navigator.userAgent.toLowerCase());if(Howler._navigator&&Howler._navigator.standalone&&!safari||Howler._navigator&&!Howler._navigator.standalone&&!safari){Howler.usingWebAudio=false;}}
if(Howler.usingWebAudio){Howler.masterGain=(typeof Howler.ctx.createGain==='undefined')?Howler.ctx.createGainNode():Howler.ctx.createGain();Howler.masterGain.gain.setValueAtTime(Howler._muted?0:1,Howler.ctx.currentTime);Howler.masterGain.connect(Howler.ctx.destination);}
Howler._setup();};if(typeof define==='function'&&define.amd){define([],function(){return{Howler:Howler,Howl:Howl};});}
if(typeof exports!=='undefined'){exports.Howler=Howler;exports.Howl=Howl;}
if(typeof window!=='undefined'){window.HowlerGlobal=HowlerGlobal;window.Howler=Howler;window.Howl=Howl;window.Sound=Sound;}else if(typeof global!=='undefined'){global.HowlerGlobal=HowlerGlobal;global.Howler=Howler;global.Howl=Howl;global.Sound=Sound;}})();

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net50</TargetFramework>
<TargetFramework>net70</TargetFramework>
<DebugType>full</DebugType>
</PropertyGroup>
@ -34,4 +34,9 @@
<Content Remove="music\**" />
</ItemGroup>
<ItemGroup>
<Folder Include="fonts" />
</ItemGroup>
</Project>

View file

@ -1,47 +1,107 @@
/* inconsolata-regular - latin */
@font-face {
font-family: 'Inconsolata';
font-style: normal;
font-weight: 400;
src: local(''),
url('../fonts/inconsolata-v21-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/inconsolata-v21-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
/* ubuntu-mono-regular - latin */
@font-face {
font-family: 'Ubuntu Mono';
font-style: normal;
font-weight: 400;
src: local(''),
url('../fonts/ubuntu-mono-v10-latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
url('../fonts/ubuntu-mono-v10-latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
}
body {
background-color: #161616;
background-color: #161616;
}
h2, h2 a {
font-family: 'Inconsolata', 'monospace';
font-size: 11pt;
color: #afafaf;
margin-top: 6px;
margin-bottom: 2px;
font-family: 'Inconsolata', 'monospace';
font-size: 11pt;
color: #afafaf;
margin-top: 6px;
margin-bottom: 2px;
}
#container, a {
font-family: 'Ubuntu Mono', 'monospace';
font-size: 11pt;
color: #d8c18c;
text-decoration: none;
margin-top: 3px;
margin-bottom: 3px;
font-family: 'Ubuntu Mono', 'monospace';
font-size: 11pt;
color: #d8c18c;
text-decoration: none;
margin-top: 3px;
margin-bottom: 3px;
}
.playing {
color: #6b9969;
.playing, .active {
color: #6b9969;
}
.entry-muted {
color: #d88777;
color: #d88777;
}
.action {
font-size: 10pt;
color: #cc7851;
font-size: 10pt;
color: #cc7851;
}
.action-muted {
font-size: 10pt;
color: #e5e5e5;
font-size: 10pt;
color: #e5e5e5;
}
#container {
margin-left: 10%;
margin-right: 10%;
margin-left: 10%;
margin-right: 10%;
}
li {
list-style: none;
list-style: none;
}
#state, #repeatButton, #continuousButton {
cursor: pointer;
}
.selected {
color: #8cd8cd;
}
/* Scrollbar colors */
/* Works on Firefox */
* {
scrollbar-width: thin;
scrollbar-color: grey #161616;
}
/* Works on Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 6px;
}
*::-webkit-scrollbar-track {
background: #161616;
}
*::-webkit-scrollbar-thumb {
background-color: grey;
border-radius: 20px;
}
::-moz-selection { /* Code for Firefox */
color: #161616;
background: #d8c18c;
}
::selection {
color: #161616;
background: #d8c18c;
}

View file

@ -1,169 +1,282 @@
let gstate = "idle";
'use strict';
const audioPlayer = new Audio();
let selectedItem = 0;
let playingItem = 0;
let playerState = 'idle';
let continuous = true;
let repeat = false;
let continuelist = true;
let queue = [];
let total = 0;
let index = 0;
let onlyDirs = true;
let sound = new Howl({
src: [''],
format: "mp3",
html5: true
});
const handleKeyEvent = (event) => {
if (event.ctrlKey === true || event.altKey === true || event.metaKey === true) return;
event.preventDefault();
event.stopPropagation();
setInterval(function () {
updateState();
}, 1000);
switch (event.key) {
case ' ':
case 'p':
if (onlyDirs !== false) return;
if (playerState === 'idle' && total !== 0) {
if (document.getElementById(playingItem).classList.contains('dir')) {
return nextTrack();
}
window.onload = function () {
initState();
updateState()
playSong(playingItem)
} else {
togglePlayback();
}
break;
case '>':
nextTrack();
break;
case '<':
previousTrack();
break;
case 'r':
toggleRepeat();
break;
case 'c':
toggleContinue();
break;
case 'ArrowUp':
selectPreviousItem();
break;
case 'ArrowDown':
selectNextItem();
break;
case 'ArrowLeft':
if (audioPlayer.currentTime < 10) {
audioPlayer.currentTime = 0;
} else {
audioPlayer.currentTime = audioPlayer.currentTime - 10;
}
break;
case 'ArrowRight':
audioPlayer.currentTime = audioPlayer.currentTime + 10;
break;
case 'Enter':
if (document.activeElement.href) {
document.activeElement.click();
} else {
document.getElementById(selectedItem).click();
}
break;
case 'Escape':
document.getElementById('back').click();
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
audioPlayer.currentTime = audioPlayer.duration / 100 * (event.key * 10);
break;
}
};
window.onkeyup = function (event) {
if (event.key === " " || event.key === "p") {
if (gstate !== "idle") {
togglePlayback();
} else {
playSong(queue[0])
}
}
else if (event.key === "r") {
toggleRepeat();
}
else if (event.key === "c") {
toggleContinue();
}
else if (event.key === "ArrowUp") {
previousTrack();
}
else if (event.key === "ArrowDown") {
const initState = () => {
const dirElements = document.querySelectorAll('.dir');
const fileElements = document.querySelectorAll('.file');
let id = 0;
document.getElementById('state').addEventListener('click', togglePlayback)
document.getElementById('repeatButton').addEventListener('click', toggleRepeat)
document.getElementById('continuousButton').addEventListener('click', toggleContinue)
audioPlayer.addEventListener('canplay', () => {
audioPlayer.play();
});
audioPlayer.addEventListener('play', () => {
setPlayerState('playing');
});
audioPlayer.addEventListener('pause', () => {
setPlayerState('paused');
});
audioPlayer.addEventListener('error', () => {
setPlayerState('error loading track');
});
audioPlayer.addEventListener('ended', () => {
setPlayerState('idle');
nextTrack();
}
else if (event.key === "ArrowLeft") {
if (sound.seek() < 10) {
sound.seek(0);
} else {
sound.seek(sound.seek()-10);
}
}
else if (event.key === "ArrowRight") {
sound.seek(sound.seek()+10);
}
else if (event.key === "Escape") {
document.getElementById("back").click();
}
};
});
audioPlayer.addEventListener('timeupdate', () => {
updatePlayerState();
});
function togglePlayback() {
if (sound.playing())
sound.pause();
else
sound.play();
dirElements.forEach((element) => {
element.id = id++;
});
fileElements.forEach((element) => {
element.id = id++;
onlyDirs = false;
element.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
playSong(event.target.id);
});
});
total = id;
updatePlayerState();
updateButtonState();
}
function setState(state) {
gstate = state;
console.log("now in state: " + state);
updateState();
const setPlayerState = (state) => {
playerState = state;
console.log('now in state: ' + state);
updatePlayerState();
}
const updatePlayerState = () => {
let statestr = '[' + playerState;
function playSong(url) {
if (document.getElementsByClassName("playing").length > 0)
document.getElementsByClassName("playing")[0].classList.remove("playing");
index = queue.indexOf(url);
sound.stop();
sound.unload();
sound = null;
delete sound;
if (!audioPlayer.paused) {
statestr += ' ' + formatTime(audioPlayer.currentTime) + '/' + formatTime(audioPlayer.duration);
}
sound = new Howl({
src: [url],
html5: true
});
setState("loading");
sound.play();
sound.loop(repeat);
document.querySelectorAll('[onclick="playSong(\''+url+'\')"]')[0].classList.add("playing");
sound.on("play", function () {
setState("playing");
});
sound.on("loaderror", function () {
setState("error loading track")
});
sound.on("playerror", function () {
setState("error opening audio device")
});
sound.on("end", function () {
setState("idle");
nextTrack()
});
sound.on("pause", function () {
setState("paused")
});
statestr += ']';
document.getElementById('state').innerHTML = statestr;
}
function toggleRepeat() {
const updateButtonState = () => {
if (repeat !== false) {
document.getElementById('repeatButton').classList.add('active');
} else {
document.getElementById('repeatButton').classList.remove('active');
}
if (continuous !== false) {
document.getElementById('continuousButton').classList.add('active');
} else {
document.getElementById('continuousButton').classList.remove('active');
}
}
const playSong = (id) => {
const element = document.getElementById(id);
if (element === null) return;
if (element.classList.contains('dir')) return;
if (document.getElementsByClassName('playing').length > 0) {
document.getElementsByClassName('playing')[0].classList.remove('playing');
}
if (document.getElementsByClassName('selected').length > 0) {
document.getElementsByClassName('selected')[0].classList.remove('selected');
}
audioPlayer.pause()
playingItem = element.id;
element.classList.add('playing');
audioPlayer.src = element.href;
setPlayerState('loading');
audioPlayer.load();
}
const togglePlayback = () => {
if (audioPlayer.paused) {
audioPlayer.play();
} else {
audioPlayer.pause();
}
}
const toggleRepeat = () => {
repeat = !repeat;
continuelist = !repeat;
sound.loop(repeat);
updateState();
continuous = !repeat;
audioPlayer.loop = repeat;
updateButtonState();
}
function toggleContinue() {
continuelist = !continuelist;
updateState();
const toggleContinue = () => {
continuous = !continuous;
updateButtonState();
}
function updateState() {
document.getElementById("state").setAttribute('onclick', 'togglePlayback()');
let statestr = "[";
statestr += gstate;
if (sound.playing())
statestr += " " + formatTime(Math.round(sound.seek())) + "/" + formatTime(Math.round(sound.duration()));
const previousTrack = () => {
if (!continuous) return;
if (playingItem-- === 0) playingItem = total - 1;
statestr += "]";
document.getElementById("state").innerHTML = statestr;
let flags = "[";
if (repeat)
flags += '<a onclick="toggleRepeat()" href="#" style="color:#6b9969">R</a>';
else
flags += '<a onclick="toggleRepeat()" href="#">R</a>';
if (continuelist)
flags += '<a onclick="toggleContinue()" href="#" style="color:#6b9969">C</a>';
else
flags += '<a onclick="toggleContinue()" href="#">C</a>';
if (document.getElementById(playingItem).classList.contains('dir')) {
return previousTrack();
}
document.getElementById("flags").innerHTML = flags + "]";
playSong(playingItem);
}
function initState() {
total = queue.length;
const nextTrack = () => {
if (!continuous) return;
if (++playingItem === total) playingItem = 0;
if (document.getElementById(playingItem).classList.contains('dir')) {
return nextTrack();
}
playSong(playingItem);
}
function previousTrack() {
if (index-- === 0)
index = total-1;
if (continuelist) {
playSong(queue[index])
const selectPreviousItem = () => {
if (selectedItem-- === 0) selectedItem = total - 1;
updateSelectedItem();
}
const selectNextItem = () => {
if (selectedItem === 0 && document.getElementsByClassName('selected').length === 0) {
document.getElementById(selectedItem).classList.add('selected');
} else {
if (++selectedItem === total) selectedItem = 0;
updateSelectedItem();
}
}
function nextTrack() {
if (++index === total)
index = 0;
if (continuelist) {
playSong(queue[index])
const updateSelectedItem = () => {
if (document.getElementsByClassName('selected').length > 0) {
document.getElementsByClassName('selected')[0].classList.remove('selected');
}
document.getElementById(selectedItem).classList.add('selected');
}
function formatTime(secs) {
const formatTime = (secs) => {
secs = Math.round(secs);
const minutes = Math.floor(secs / 60) || 0;
const seconds = (secs - minutes * 60) || 0;
return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
}
document.addEventListener('DOMContentLoaded', initState);
document.addEventListener('keydown', handleKeyEvent);
function copyToClipboard(str) {
navigator.clipboard.writeText(str);
}