commit d9c776e1d1032bfd35746ce541e53c79dbc62e9f Author: Laura Date: Sat May 11 23:23:27 2019 +0200 Initial commit fix repeat added simple keyboard control add preloading tracks add plain view ("api") fix memory leak, add streaming responsive viewport Mobile web capable diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c5eb50 --- /dev/null +++ b/.gitignore @@ -0,0 +1,225 @@ +# The following command works for downloading when using Git for Windows: +# curl -LOf http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore +# +# Download this file using PowerShell v3 under Windows with the following comand: +# Invoke-WebRequest https://gist.githubusercontent.com/kmorcinek/2710267/raw/ -OutFile .gitignore +# +# or wget: +# wget --no-check-certificate http://gist.githubusercontent.com/kmorcinek/2710267/raw/.gitignore + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +[Bb]in/ +[Oo]bj/ +# build folder is nowadays used for build scripts and should not be ignored +#build/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# OS generated files # +.DS_Store* +Icon? + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings +modulesbin/ +tempbin/ + +# EPiServer Site file (VPP) +AppData/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# vim +*.txt~ +*.swp +*.swo + +# Temp files when opening LibreOffice on ubuntu +.~lock.* + +# svn +.svn + +# CVS - Source Control +**/CVS/ + +# Remainings from resolving conflicts in Source Control +*.orig + +# SQL Server files +**/App_Data/*.mdf +**/App_Data/*.ldf +**/App_Data/*.sdf + + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# SASS Compiler cache +.sass-cache + +# Visual Studio 2014 CTP +**/*.sln.ide + +# Visual Studio temp something +.vs/ + +# dotnet stuff +project.lock.json + +# VS 2015+ +*.vc.vc.opendb +*.vc.db + +# Rider +.idea/ + +# Visual Studio Code +.vscode/ + +# Output folder used by Webpack or other FE stuff +**/node_modules/* +**/wwwroot/* + +# SpecFlow specific +*.feature.cs +*.feature.xlsx.* +*.Specs_*.html + +##### +# End of core ignore list, below put you custom 'per project' settings (patterns or path) +##### diff --git a/Pages/Error.cshtml b/Pages/Error.cshtml new file mode 100644 index 0000000..6f92b95 --- /dev/null +++ b/Pages/Error.cshtml @@ -0,0 +1,26 @@ +@page +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

diff --git a/Pages/Error.cshtml.cs b/Pages/Error.cshtml.cs new file mode 100644 index 0000000..912bdf1 --- /dev/null +++ b/Pages/Error.cshtml.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace webmusic.Pages +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} \ No newline at end of file diff --git a/Pages/Index.cshtml b/Pages/Index.cshtml new file mode 100644 index 0000000..fafb3e5 --- /dev/null +++ b/Pages/Index.cshtml @@ -0,0 +1,31 @@ +@page +@using System.Web +@model IndexModel +@{ + ViewData["Title"] = "webmusic"; +} +@if (Model.path.Contains("..")) +{ + return; +} +

@Model.displaypath

+ [..] + Go back
+ [--] + Download m3u

+@foreach (var dir in Model.dirs) +{ + [--] + @dir +
+} +@foreach (var file in Model.files) +{ + var jspath = Model.Encode(Model.fullpath); + var jsfile = jspath + "/" + Model.Encode(file); + + + [DL] + @file +
+} \ No newline at end of file diff --git a/Pages/Index.cshtml.cs b/Pages/Index.cshtml.cs new file mode 100644 index 0000000..1fbc2e4 --- /dev/null +++ b/Pages/Index.cshtml.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using Microsoft.AspNetCore.Mvc.RazorPages; + + +namespace webmusic.Pages +{ + public class IndexModel : PageModel + { + public static string root = "music"; + public string path = ""; + public string displaypath = ""; + public string path_oneup = ""; + public string fullpath = ""; + public List dirs = new List(); + public List files = new List(); + public void OnGet() + { + if (Request.QueryString.HasValue) + path = HttpUtility.UrlDecode(Request.QueryString.Value.Remove(0,1)); + if (path.Contains("..")) + { + Response.Redirect("/Error"); + return; + } + path_oneup = Regex.Match(path, @".*(?=\/)").Value; + fullpath = root + path; + displaypath = string.IsNullOrWhiteSpace(path) ? "/" : path; + dirs = Directory.GetDirectories(fullpath).Select(Path.GetFileName).ToList(); + dirs.Sort(); + files = Directory.GetFiles(fullpath).Select(Path.GetFileName).ToList(); + files.RemoveAll(p => p.EndsWith(".m3u")); + files.Sort(new NumericComparer()); + } + + public string Encode(string str) + { + return str.Replace("\"", "%22").Replace("'", "%27") + .Replace("?", "%3F").Replace("&", "%26") + .Replace(" ", "%20"); + } + + public class NumericComparer : IComparer + { + public int Compare(string x, string y) + { + var regex = new Regex(@"^\d+"); + + // run the regex on both strings + var xRegexResult = regex.Match(x); + var yRegexResult = regex.Match(y); + + // check if they are both numbers + if (xRegexResult.Success && yRegexResult.Success) + { + return int.Parse(xRegexResult.Value).CompareTo(int.Parse(yRegexResult.Value)); + } + + // otherwise return as string comparison + return string.Compare(x, y, StringComparison.Ordinal); + } + } + } +} \ No newline at end of file diff --git a/Pages/Shared/_Layout.cshtml b/Pages/Shared/_Layout.cshtml new file mode 100644 index 0000000..38a33a2 --- /dev/null +++ b/Pages/Shared/_Layout.cshtml @@ -0,0 +1,37 @@ + + + + + + + + Music + + + + + + + + + + + + + + + + + + + + + + + + +
+@RenderBody() +
+ + diff --git a/Pages/_ViewImports.cshtml b/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..3ddae19 --- /dev/null +++ b/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using webmusic +@namespace webmusic.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Pages/_ViewStart.cshtml b/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..a5f1004 --- /dev/null +++ b/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/Pages/plain.cshtml b/Pages/plain.cshtml new file mode 100644 index 0000000..1eb63a6 --- /dev/null +++ b/Pages/plain.cshtml @@ -0,0 +1,18 @@ +@page +@using System.Web +@model IndexModel +@{ + Layout = null; +} +@if (Model.path.Contains("..")) +{ + return; +} +@foreach (var dir in Model.dirs) +{ +@Html.Raw(dir+"\n") +} +@foreach (var file in Model.files) +{ +@Html.Raw(file+"\n") +} \ No newline at end of file diff --git a/Pages/plain.cshtml.cs b/Pages/plain.cshtml.cs new file mode 100644 index 0000000..01589af --- /dev/null +++ b/Pages/plain.cshtml.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web; +using Microsoft.AspNetCore.Mvc.RazorPages; + + +namespace webmusic.Pages +{ + public class PlainModel : PageModel + { + public static string root = "music"; + public string path = ""; + public string displaypath = ""; + public string path_oneup = ""; + public string fullpath = ""; + public List dirs = new List(); + public List files = new List(); + public void OnGet() + { + if (Request.QueryString.HasValue) + path = HttpUtility.UrlDecode(Request.QueryString.Value.Remove(0,1)); + if (path.Contains("..")) + { + Response.Redirect("/Error"); + return; + } + path_oneup = Regex.Match(path, @".*(?=\/)").Value; + fullpath = root + path; + displaypath = string.IsNullOrWhiteSpace(path) ? "/" : path; + dirs = Directory.GetDirectories(fullpath).Select(Path.GetFileName).ToList(); + dirs.Sort(); + files = Directory.GetFiles(fullpath).Select(Path.GetFileName).ToList(); + files.RemoveAll(p => p.EndsWith(".m3u")); + files.Sort(new NumericComparer()); + } + + public class NumericComparer : IComparer + { + public int Compare(string x, string y) + { + var regex = new Regex(@"^\d+"); + + // run the regex on both strings + var xRegexResult = regex.Match(x); + var yRegexResult = regex.Match(y); + + // check if they are both numbers + if (xRegexResult.Success && yRegexResult.Success) + { + return int.Parse(xRegexResult.Value).CompareTo(int.Parse(yRegexResult.Value)); + } + + // otherwise return as string comparison + return string.Compare(x, y, StringComparison.Ordinal); + } + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..b0eb753 --- /dev/null +++ b/Program.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace webmusic +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseWebRoot(".") + .UseStartup(); + } +} \ No newline at end of file diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..c786b0b --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,20 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:63053", + "sslPort": 44395 + } + }, + "profiles": { + "webmusic": { + "commandName": "Project", + "launchBrowser": true, + "applicationUrl": "http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/Startup.cs b/Startup.cs new file mode 100644 index 0000000..42210d1 --- /dev/null +++ b/Startup.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace webmusic +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.Configure(options => + { + // This lambda determines whether user consent for non-essential cookies is needed for a given request. + options.CheckConsentNeeded = context => true; + options.MinimumSameSitePolicy = SameSiteMode.None; + }); + + + services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".flac"] = "audio/flac"; + provider.Mappings[".m3u"] = "audio/m3u"; + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else + { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseStaticFiles(new StaticFileOptions() + { + ContentTypeProvider = provider + }); + app.UseCookiePolicy(); + + app.UseMvc(); + } + } +} \ No newline at end of file diff --git a/app.config b/app.config new file mode 100644 index 0000000..a6ad828 --- /dev/null +++ b/app.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..9c53c8c --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning", + "System": "Warning", + "Microsoft": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..def9159 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/favicon/android-icon-144x144.png b/favicon/android-icon-144x144.png new file mode 100644 index 0000000..83a6da6 Binary files /dev/null and b/favicon/android-icon-144x144.png differ diff --git a/favicon/android-icon-192x192.png b/favicon/android-icon-192x192.png new file mode 100644 index 0000000..d26506a Binary files /dev/null and b/favicon/android-icon-192x192.png differ diff --git a/favicon/android-icon-36x36.png b/favicon/android-icon-36x36.png new file mode 100644 index 0000000..d129da9 Binary files /dev/null and b/favicon/android-icon-36x36.png differ diff --git a/favicon/android-icon-48x48.png b/favicon/android-icon-48x48.png new file mode 100644 index 0000000..2962a03 Binary files /dev/null and b/favicon/android-icon-48x48.png differ diff --git a/favicon/android-icon-72x72.png b/favicon/android-icon-72x72.png new file mode 100644 index 0000000..9631085 Binary files /dev/null and b/favicon/android-icon-72x72.png differ diff --git a/favicon/android-icon-96x96.png b/favicon/android-icon-96x96.png new file mode 100644 index 0000000..bfdbd2d Binary files /dev/null and b/favicon/android-icon-96x96.png differ diff --git a/favicon/apple-icon-114x114.png b/favicon/apple-icon-114x114.png new file mode 100644 index 0000000..acc5800 Binary files /dev/null and b/favicon/apple-icon-114x114.png differ diff --git a/favicon/apple-icon-120x120.png b/favicon/apple-icon-120x120.png new file mode 100644 index 0000000..f7e2e9e Binary files /dev/null and b/favicon/apple-icon-120x120.png differ diff --git a/favicon/apple-icon-144x144.png b/favicon/apple-icon-144x144.png new file mode 100644 index 0000000..83a6da6 Binary files /dev/null and b/favicon/apple-icon-144x144.png differ diff --git a/favicon/apple-icon-152x152.png b/favicon/apple-icon-152x152.png new file mode 100644 index 0000000..f8902b2 Binary files /dev/null and b/favicon/apple-icon-152x152.png differ diff --git a/favicon/apple-icon-180x180.png b/favicon/apple-icon-180x180.png new file mode 100644 index 0000000..0326f6c Binary files /dev/null and b/favicon/apple-icon-180x180.png differ diff --git a/favicon/apple-icon-57x57.png b/favicon/apple-icon-57x57.png new file mode 100644 index 0000000..6b5f106 Binary files /dev/null and b/favicon/apple-icon-57x57.png differ diff --git a/favicon/apple-icon-60x60.png b/favicon/apple-icon-60x60.png new file mode 100644 index 0000000..be79aa7 Binary files /dev/null and b/favicon/apple-icon-60x60.png differ diff --git a/favicon/apple-icon-72x72.png b/favicon/apple-icon-72x72.png new file mode 100644 index 0000000..9631085 Binary files /dev/null and b/favicon/apple-icon-72x72.png differ diff --git a/favicon/apple-icon-76x76.png b/favicon/apple-icon-76x76.png new file mode 100644 index 0000000..0c3b25e Binary files /dev/null and b/favicon/apple-icon-76x76.png differ diff --git a/favicon/apple-icon-precomposed.png b/favicon/apple-icon-precomposed.png new file mode 100644 index 0000000..656d3dd Binary files /dev/null and b/favicon/apple-icon-precomposed.png differ diff --git a/favicon/apple-icon.png b/favicon/apple-icon.png new file mode 100644 index 0000000..656d3dd Binary files /dev/null and b/favicon/apple-icon.png differ diff --git a/favicon/browserconfig.xml b/favicon/browserconfig.xml new file mode 100644 index 0000000..c554148 --- /dev/null +++ b/favicon/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/favicon/favicon-16x16.png b/favicon/favicon-16x16.png new file mode 100644 index 0000000..fb8aa2a Binary files /dev/null and b/favicon/favicon-16x16.png differ diff --git a/favicon/favicon-32x32.png b/favicon/favicon-32x32.png new file mode 100644 index 0000000..85aa6d4 Binary files /dev/null and b/favicon/favicon-32x32.png differ diff --git a/favicon/favicon-96x96.png b/favicon/favicon-96x96.png new file mode 100644 index 0000000..bfdbd2d Binary files /dev/null and b/favicon/favicon-96x96.png differ diff --git a/favicon/favicon.ico b/favicon/favicon.ico new file mode 100644 index 0000000..af00b6f Binary files /dev/null and b/favicon/favicon.ico differ diff --git a/favicon/ms-icon-144x144.png b/favicon/ms-icon-144x144.png new file mode 100644 index 0000000..83a6da6 Binary files /dev/null and b/favicon/ms-icon-144x144.png differ diff --git a/favicon/ms-icon-150x150.png b/favicon/ms-icon-150x150.png new file mode 100644 index 0000000..359dbfc Binary files /dev/null and b/favicon/ms-icon-150x150.png differ diff --git a/favicon/ms-icon-310x310.png b/favicon/ms-icon-310x310.png new file mode 100644 index 0000000..c7b5ca1 Binary files /dev/null and b/favicon/ms-icon-310x310.png differ diff --git a/favicon/ms-icon-70x70.png b/favicon/ms-icon-70x70.png new file mode 100644 index 0000000..e3964e5 Binary files /dev/null and b/favicon/ms-icon-70x70.png differ diff --git a/howler.core.js b/howler.core.js new file mode 100644 index 0000000..1b78473 --- /dev/null +++ b/howler.core.js @@ -0,0 +1,162 @@ +/*! +* 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=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;i0?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=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;i0)?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((tofrom&&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=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=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=0){Howler._howls.splice(index,1);} +var remCache=true;for(i=0;i=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=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=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=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;i0){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;}})(); \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..3efa910 --- /dev/null +++ b/manifest.json @@ -0,0 +1,44 @@ +{ + "short_name": "Music", + "name": "Music", + "icons": [ + { + "src": "\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + } + ], + "display": "standalone", + "start_url": "/" +} \ No newline at end of file diff --git a/webmusic.csproj b/webmusic.csproj new file mode 100644 index 0000000..866c194 --- /dev/null +++ b/webmusic.csproj @@ -0,0 +1,37 @@ + + + + netcoreapp2.2 + full + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/webmusic.css b/webmusic.css new file mode 100644 index 0000000..8b370dc --- /dev/null +++ b/webmusic.css @@ -0,0 +1,43 @@ +body { + background-color: #161616; +} + +h2, h2 a { + 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; +} + +.playing { + color: #6b9969; +} + +.entry-muted { + color: #d88777; +} + +.action { + font-size: 10pt; + color: #cc7851; +} + +.action-muted { + font-size: 10pt; + color: #e5e5e5; +} + +#container { + margin-left: 10%; + margin-right: 10%; +} \ No newline at end of file diff --git a/webmusic.js b/webmusic.js new file mode 100644 index 0000000..d90911f --- /dev/null +++ b/webmusic.js @@ -0,0 +1,158 @@ +let gstate = "idle"; +let repeat = false; +let continuelist = true; +let queue = []; +let total = 0; +let index = 0; + +let sound = new Howl({ + src: [''], + format: "mp3", + html5: true +}); + +setInterval(function () { + updateState(); +}, 1000); + +window.onload = function () { + initState(); + updateState() +}; + +document.addEventListener("keypress", function onEvent(event) { + if (event.key === "p") { + togglePlayback(); + } + else if (event.key === "r") { + toggleRepeat(); + } + else if (event.key === "c") { + toggleContinue(); + } +}); + + +function togglePlayback() { + if (sound.playing()) + sound.pause(); + else + sound.play(); +} + +function setState(state) { + gstate = state; + console.log("now in state: " + state); + updateState(); +} + + +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; + + 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") + }); +} + +function toggleRepeat() { + repeat = !repeat; + continuelist = !repeat; + sound.loop(repeat); + updateState(); +} + +function toggleContinue() { + continuelist = !continuelist; + updateState(); +} + +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())); + + statestr += "]"; + document.getElementById("state").innerHTML = statestr; + let flags = "["; + if (repeat) + flags += 'R'; + else + flags += 'R'; + if (continuelist) + flags += 'C'; + else + flags += 'C'; + + document.getElementById("flags").innerHTML = flags + "]"; +} + +function initState() { + total = queue.length; +} + +function nextTrack() { + if (++index === total) + index = 0; + if (continuelist) { + playSong(queue[index]) + } +} + +function formatTime(secs) { + const minutes = Math.floor(secs / 60) || 0; + const seconds = (secs - minutes * 60) || 0; + return minutes + ':' + (seconds < 10 ? '0' : '') + seconds; +} + +function download(filename, text) { + var element = document.createElement('a'); + element.setAttribute('href', 'data:audio/m3u;charset=utf-8,' + encodeURIComponent(text)); + element.setAttribute('download', filename); + + element.style.display = 'none'; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +function downloadm3u() { + let m3utext = ""; + queue.forEach(function(item, index){ + m3utext+=window.location.protocol + "//" + window.location.host + "/" + item + "\n"; + }); + download("play.m3u", m3utext) +} \ No newline at end of file diff --git a/webmusic.sln b/webmusic.sln new file mode 100644 index 0000000..c5e6159 --- /dev/null +++ b/webmusic.sln @@ -0,0 +1,16 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "webmusic", "webmusic.csproj", "{C0A4B1C0-C3CE-4820-A1E6-858373C956CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C0A4B1C0-C3CE-4820-A1E6-858373C956CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0A4B1C0-C3CE-4820-A1E6-858373C956CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0A4B1C0-C3CE-4820-A1E6-858373C956CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0A4B1C0-C3CE-4820-A1E6-858373C956CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal