mirror of
https://github.com/ragetrip/audio-pluglist.git
synced 2026-07-22 06:44:43 +00:00
Add files via upload
1.2.5 deployment
This commit is contained in:
parent
8a9d05a642
commit
1a14f745b0
6 changed files with 405 additions and 138 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -1,6 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.3 — Initial public release
|
||||
- First GitHub release of **Audio PlugList**.
|
||||
- Features: Folder/Link playlists, footer mini-player, shuffle/repeat, fade-out stop, artist/album columns, and scan tools.
|
||||
## 1.2.5 - First aimed community release
|
||||
- First aimed community release of **Audio Pluglist**.
|
||||
- Added **External link** support.
|
||||
- Added **cleaner** UI sizing.
|
||||
- Added **mobile support**.
|
||||
|
||||
|
||||
## 1.0.3 - Initial public release
|
||||
- First GitHub release of **Audio PlugList**.
|
||||
- Features: Folder/Link playlists, footer mini-player, shuffle/repeat, fade-out stop, artist/album columns, and scan tools.
|
||||
94
README.md
94
README.md
|
|
@ -1,16 +1,8 @@
|
|||
# Audio PlugList
|
||||
|
||||
[](https://buymeacoffee.com/ragetrip)
|
||||
Playlist-based audio player for Obsidian. Point a playlist at a vault **folder** (auto-index audio files), an external **link** (YouTube, Spotify, Deezer, SoundCloud, or External Media File), then control playback from a clean **tab view** and a compact **footer mini-player** with shuffle/repeat and optional fade-out stop.
|
||||
|
||||
|
||||
Multi playlist-based audio player. Point a playlist at a vault **folder** (auto-index audio files) or a single external **link**, then control playback from a clean **tab view** and a compact **footer mini‑player** with shuffle/repeat and optional fade‑out stop.
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots / Audio Pluglist
|
||||
| Easy To Use |
|
||||
| --- |
|
||||
|  |
|
||||
> **Version:** 1.2.5 · **Obsidian:** 1.8.0+ · **Platforms:** Desktop & Mobile
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -18,56 +10,35 @@ Multi playlist-based audio player. Point a playlist at a vault **folder** (auto-
|
|||
|
||||
- **Playlists**
|
||||
- Create from a **Folder** (recursively indexes supported audio files).
|
||||
- Create from a **Link** (direct audio URL; subject to browser/CORS).
|
||||
- **Footer mini‑player** with segmented controls (⏮ ⏯ ⏭ ⏹ 🔀 🔁), seek, volume, and a marquee **Now Playing** title.
|
||||
- Create from a **Link** (direct audio URL).
|
||||
- **Footer mini-player** with segmented controls (⏮ ⏯ ⏭ ⏹ 🔀 🔁), seek, volume, and a marquee **Now Playing** title.
|
||||
- **Tab view** showing current playlist, track list, and playback controls.
|
||||
- **Shuffle / Repeat** (Off · All · One).
|
||||
- **Fade‑out on Stop** with adjustable duration.
|
||||
- **Fade-out on Stop** with adjustable duration.
|
||||
- **Optional columns**: show Artist and/or Album in the track list.
|
||||
- **Quick re-scan**: index one playlist or **Scan All** from settings.
|
||||
- **File name parser**: `Artist — Title` (em dash or hyphen) convenience.
|
||||
- **File name parser**: `Artist - Title` (em dash or hyphen) convenience.
|
||||
|
||||
**Supported formats:** `mp3`, `wav`, `m4a`, `flac`, `ogg`, `aac`.
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots / Easily Create Multiple Playlists
|
||||
| Open Menu | Add Playlist |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots / Optional Footer Mini-Player
|
||||
| Tab Mode | Footer Mini-Player |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
---
|
||||
|
||||
## 📸 Screenshots / Dropdown Playlists Selector
|
||||
| Tab Mode | Footer Mini-Player |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
---
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
### Option A: Manual (first GitHub upload)
|
||||
1. Download the latest release assets (`main.js`, `manifest.json`, `styles.css`) from this repository.
|
||||
2. Create a folder inside your vault at:
|
||||
`/plugins/audio-pluglist/`
|
||||
`.obsidian/plugins/audio-pluglist/`
|
||||
3. Copy the three files into that folder.
|
||||
4. In Vault: **Settings → Community plugins → Installed plugins → Enable “Audio PlugList”.**
|
||||
4. In Obsidian: **Settings → Community plugins → Installed plugins → Enable “Audio PlugList”.**
|
||||
|
||||
### Option B: BRAT (before it’s in the Community store)
|
||||
If you use the **BRAT** plugin:
|
||||
1. In BRAT, add this repo’s URL.
|
||||
1. In BRAT, add this repo’s URL: https://github.com/ragetrip/audio-pluglist
|
||||
2. Let BRAT install and enable the plugin.
|
||||
|
||||
### Community Store (once approved)
|
||||
After the plugin is accepted into the Community Plugin store, you’ll be able to install and update it directly from **Settings → Community plugins → Browse**.
|
||||
After the plugin is accepted into the Obsidian Community Plugin store, you’ll be able to install and update it directly from **Settings → Community plugins → Browse**.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -76,16 +47,17 @@ After the plugin is accepted into the Community Plugin store, you’ll be able t
|
|||
1. Open **Settings → Audio PlugList**.
|
||||
2. Under **Add New Playlist**, choose:
|
||||
- **From Folder** → pick a vault folder to index audio.
|
||||
- **From Link** → paste a direct audio URL (e.g. `https://…/song.mp3`).
|
||||
*Note:* External links must be direct audio files. Many services block playback via CORS.
|
||||
- **From Link** → paste a direct URL.
|
||||
*Note:* Please be aware that some services block external playback.
|
||||
3. Click **Scan** on a playlist (or **Scan All**).
|
||||
4. Open the view via **Ribbon button** (music icon) or the command **“Open Audio PlugList.”**
|
||||
5. Use the **tab controls** or the **footer mini‑player** to play, pause, skip, seek, adjust volume, toggle shuffle, and cycle repeat.
|
||||
5. Use the **tab controls** or the **footer mini-player** to play, pause, skip, seek, adjust volume, toggle shuffle, and cycle repeat.
|
||||
|
||||
**Tips**
|
||||
- File names like `Artist - Title.mp3` or `Artist — Title.flac` are parsed into Artist/Title automatically.
|
||||
- File names like `Artist - Title.mp3` or `Artist - Title.flac` are parsed into Artist/Title automatically.
|
||||
- Turn on **Show Artist** / **Show Album** columns if you prefer richer lists.
|
||||
- Enable **Fade out on Stop** and set a custom **Fade duration (ms)** if you like gentler stops.
|
||||
- Footer mini-player will not directly control external link embed controls, but will open the playlist to the working Tab view with controls.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -94,12 +66,12 @@ After the plugin is accepted into the Community Plugin store, you’ll be able t
|
|||
- **Playlists (dynamic list)**
|
||||
- **Name** (optional label used in menus/views)
|
||||
- **Folder** (for folder playlists) + **Browse** + **Scan now**
|
||||
- **Link** (for link playlists; one direct URL)
|
||||
- **Link** (for link playlists)
|
||||
- **Delete playlist**
|
||||
- **Re-scan all playlists**
|
||||
- **Show Album in titles**
|
||||
- **Show Artist in titles**
|
||||
- **Show Footer mini‑player**
|
||||
- **Show Footer mini-player**
|
||||
- **Fade out on Stop**
|
||||
- **Fade duration (ms)**
|
||||
|
||||
|
|
@ -107,24 +79,42 @@ After the plugin is accepted into the Community Plugin store, you’ll be able t
|
|||
|
||||
## 🔍 Known Limitations
|
||||
|
||||
- **External links & CORS**: Link playlists require **direct audio URLs**; many hosts disallow cross‑origin playback.
|
||||
- **No embedded players** (YouTube/SoundCloud) in this version.
|
||||
- **External linksS**: Some link playlists require **log-in**; many hosts disallow cross-origin playback.
|
||||
- Mobile platform behavior depends on OS media policies (e.g., background playback limits).
|
||||
|
||||
---
|
||||
|
||||
## 🧰 Development
|
||||
|
||||
- **Folder structure** (what Obsidian expects):
|
||||
```text
|
||||
audio-pluglist/
|
||||
├─ main.js
|
||||
├─ manifest.json
|
||||
├─ styles.css
|
||||
└─ versions.json # optional, used by the Community listing
|
||||
```
|
||||
|
||||
- **Manifest**: `id` must be `audio-pluglist` and the version in `manifest.json` **must match** the latest entry in `versions.json` (for Community releases). This repo sets both to **1.2.5**.
|
||||
|
||||
- **Building**: This project currently ships a single `main.js`. If you adopt a toolchain later (TypeScript, bundler), make sure the output files match Obsidian’s expectations.
|
||||
|
||||
- **Commands** exposed (palette): Open, Play/Pause, Next, Previous, Stop (Fade Out), Re-scan All Playlists.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Changelog
|
||||
|
||||
### 1.0.3 — Initial public release
|
||||
- First GitHub release of **Audio PlugList**.
|
||||
- Dynamic playlists (Folder/Link), footer mini‑player, shuffle/repeat, fade‑out stop, artist/album columns, and scan tools.
|
||||
### 1.2.5 - Release Notes
|
||||
- First expected community release of **Audio PlugList**.
|
||||
- Dynamic playlists (Folder/Link), footer mini-player, shuffle/repeat, fade-out stop, artist/album columns, and scan tools.
|
||||
|
||||
---
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Bug reports and PRs are welcome. Please include:
|
||||
- Environment (OS, version), repro steps, expected vs. actual behavior.
|
||||
- Environment (OS, Obsidian version), repro steps, expected vs. actual behavior.
|
||||
- If it’s a feature request, a short mockup or user story helps.
|
||||
|
||||
---
|
||||
|
|
@ -140,4 +130,4 @@ This project is licensed under the **MIT License**. See [`LICENSE`](./LICENSE) f
|
|||
|
||||
If you like this plugin and want to support development, you can [**Buy Me a Coffee**](https://buymeacoffee.com/ragetrip).
|
||||
|
||||
Check out my other plugins and projects here: [**My GitHub Repositories**](https://github.com/ragetrip?tab=repositories).
|
||||
Check out my other Obsidian plugins and projects here: [**My GitHub Repositories**](https://github.com/ragetrip?tab=repositories).
|
||||
362
main.js
362
main.js
|
|
@ -12,6 +12,42 @@ const { Plugin, PluginSettingTab, Setting, ItemView, TFile, TFolder, Notice } =
|
|||
const VIEW_TYPE = 'audio-pluglist-view';
|
||||
const AUDIO_EXTS = { mp3:1, wav:1, m4a:1, flac:1, ogg:1, aac:1 };
|
||||
|
||||
function isDirectAudioUrl(u){ return /\.(mp3|m4a|wav|ogg|flac|aac)(\?|#|$)/i.test(u); }
|
||||
function getYouTubePlaylistId(input){
|
||||
try { const u = new URL(input); const id = u.searchParams.get("list"); if (id) return id; } catch {}
|
||||
const m = input.match(/[?&]list=([a-zA-Z0-9_-]+)/); return m ? m[1] : null;
|
||||
}
|
||||
function ytPlaylistEmbedSrc(listId){
|
||||
return `https://www.youtube-nocookie.com/embed/videoseries?list=${encodeURIComponent(listId)}&rel=0&modestbranding=1&enablejsapi=1`;
|
||||
}
|
||||
function isSoundCloudUrl(u){ return /(^|\.)soundcloud\.com\/.+/i.test(u) || /(^|\.)on\.soundcloud\.com\//i.test(u); }
|
||||
function isSoundCloudSet(u){ return /soundcloud\.com\/[^/]+\/sets\/[^/]+/i.test(u); }
|
||||
function scEmbedSrc(u, visual=false){
|
||||
const clean = u.split('#')[0].split('?')[0];
|
||||
const enc = encodeURIComponent(clean);
|
||||
const base = `https://w.soundcloud.com/player/?url=${enc}&color=%23ff5500&auto_play=false&show_teaser=true&visual=true`;
|
||||
return base;
|
||||
}
|
||||
|
||||
function fetchSoundCloudOEmbed(url, maxheight){
|
||||
try {
|
||||
const clean = url.split('#')[0].split('?')[0];
|
||||
const endpoint = `https://soundcloud.com/oembed?format=json&url=${encodeURIComponent(clean)}${maxheight?`&maxheight=${maxheight}`:''}`;
|
||||
return fetch(endpoint).then(r=>r.ok?r.json():null).then(j=>{
|
||||
if (j && j.html) return j.html;
|
||||
return null;
|
||||
}).catch(()=>null);
|
||||
} catch(e){ return Promise.resolve(null); }
|
||||
}
|
||||
function getSpotifyPlaylistId(input){
|
||||
try { const u = new URL(input); const parts = u.pathname.split("/").filter(Boolean);
|
||||
const idx = parts.indexOf("playlist"); if (idx >= 0 && parts[idx+1]) return parts[idx+1];
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
function spotifyEmbedSrc(id){ return `https://open.spotify.com/embed/playlist/${id}`; }
|
||||
|
||||
|
||||
function clamp(n, a, b){ if(n<a)return a; if(n>b)return b; return n; }
|
||||
function fmtTime(s) {
|
||||
s = Math.floor(s || 0);
|
||||
|
|
@ -102,7 +138,7 @@ FolderPicker.prototype.open = function(){
|
|||
(function(folderPath){
|
||||
row.onclick = function(){ self.onPick(folderPath); self.close(); };
|
||||
})(f);
|
||||
list.appendChild(row);
|
||||
if (list) list.appendChild(row);
|
||||
}
|
||||
}
|
||||
render();
|
||||
|
|
@ -112,7 +148,7 @@ FolderPicker.prototype.close = function(){
|
|||
if (this.overlay){ this.overlay.remove(); this.overlay = null; }
|
||||
};
|
||||
|
||||
/* Settings tab */
|
||||
// Settings tab //
|
||||
function SettingsTab(app, plugin){ PluginSettingTab.call(this, app, plugin); this.plugin = plugin; }
|
||||
SettingsTab.prototype = Object.create(PluginSettingTab.prototype);
|
||||
|
||||
|
|
@ -123,11 +159,11 @@ SettingsTab.prototype.display = function(){
|
|||
|
||||
var self = this;
|
||||
|
||||
// Add New Playlist panel
|
||||
// Add New Playlist panel //
|
||||
var addWrap = containerEl.createEl('div', { cls: 'apl-settings-addwrap' });
|
||||
addWrap.createEl('div', { text: 'Add New Playlist', cls: 'apl-addlabel' });
|
||||
|
||||
// Folder button
|
||||
// Folder button //
|
||||
new Setting(addWrap)
|
||||
.setName('From Folder')
|
||||
.setDesc('Create a playlist that scans a vault folder')
|
||||
|
|
@ -135,19 +171,23 @@ SettingsTab.prototype.display = function(){
|
|||
b.setButtonText('Add Playlist From Folder').onClick(async ()=>{
|
||||
self.plugin.settings.playlists.push({ type:'folder', name:'', folderPath:'' });
|
||||
self.plugin.tracksByPlaylist.push([]);
|
||||
if (!self.plugin.embedByPlaylist) self.plugin.embedByPlaylist = [];
|
||||
self.plugin.embedByPlaylist.push(null);
|
||||
await self.plugin.saveSettings();
|
||||
self.display();
|
||||
});
|
||||
});
|
||||
|
||||
// Link button
|
||||
// Link button //
|
||||
new Setting(addWrap)
|
||||
.setName('From Link')
|
||||
.setDesc('Create a playlist that plays a single external link')
|
||||
.setDesc('Create a playlist from a link: YouTube playlist, SoundCloud track/set, Spotify playlist, or a direct audio URL (.mp3/.m4a/.wav/.ogg/.flac/.aac)')
|
||||
.addButton(b=>{
|
||||
b.setButtonText('Add Playlist From Link').onClick(async ()=>{
|
||||
self.plugin.settings.playlists.push({ type:'link', name:'', link:'' });
|
||||
self.plugin.tracksByPlaylist.push([]);
|
||||
if (!self.plugin.embedByPlaylist) self.plugin.embedByPlaylist = [];
|
||||
self.plugin.embedByPlaylist.push(null);
|
||||
await self.plugin.saveSettings();
|
||||
self.display();
|
||||
});
|
||||
|
|
@ -158,7 +198,7 @@ SettingsTab.prototype.display = function(){
|
|||
var heading = card.createEl('div', { cls: 'setting-item setting-item-heading' });
|
||||
heading.createEl('div', { text: 'Playlist ' + (idx+1) });
|
||||
|
||||
// Name (always)
|
||||
// Name (always) //
|
||||
new Setting(card)
|
||||
.setName('Name')
|
||||
.addText(function(t){
|
||||
|
|
@ -179,7 +219,7 @@ SettingsTab.prototype.display = function(){
|
|||
.onChange(async (v) => { self.plugin.settings.playlists[idx].link = (v||'').trim(); await self.plugin.saveSettings(); });
|
||||
});
|
||||
} else {
|
||||
// Default to folder type
|
||||
// Default to folder type //
|
||||
self.plugin.settings.playlists[idx].type = 'folder';
|
||||
|
||||
new Setting(card)
|
||||
|
|
@ -213,6 +253,7 @@ SettingsTab.prototype.display = function(){
|
|||
btn.setButtonText('Delete').onClick(async ()=>{
|
||||
self.plugin.settings.playlists.splice(idx,1);
|
||||
self.plugin.tracksByPlaylist.splice(idx,1);
|
||||
if (self.plugin.embedByPlaylist) self.plugin.embedByPlaylist.splice(idx,1);
|
||||
if (self.plugin.currentPlaylist >= self.plugin.settings.playlists.length)
|
||||
self.plugin.currentPlaylist = self.plugin.settings.playlists.length - 1;
|
||||
await self.plugin.saveSettings();
|
||||
|
|
@ -220,16 +261,16 @@ SettingsTab.prototype.display = function(){
|
|||
});
|
||||
});
|
||||
|
||||
/* divider moved inside card (not needed) */
|
||||
// divider moved inside card (not needed) //
|
||||
|
||||
}
|
||||
|
||||
for (var i=0;i<self.plugin.settings.playlists.length;i++) makePlaylistSection(i);
|
||||
|
||||
// Break line above "Re-scan all playlists"
|
||||
// Break line above "Re-scan all playlists" //
|
||||
containerEl.createEl('div', { cls: 'apl-section-break' });
|
||||
|
||||
// Keep remaining settings area as-is; rebuild minimal subset
|
||||
// Keep remaining settings area as-is for better grouping. Looks better in on smaller devices //
|
||||
new Setting(containerEl)
|
||||
.setName('Re-scan all playlists')
|
||||
.addButton(function(btn){ btn.setButtonText('Scan All').onClick(() => self.plugin.indexAll()); });
|
||||
|
|
@ -277,7 +318,7 @@ SettingsTab.prototype.display = function(){
|
|||
};
|
||||
|
||||
|
||||
/* Tab View */
|
||||
// Tab View //
|
||||
function View(leaf, plugin){ ItemView.call(this, leaf); this.plugin = plugin; }
|
||||
View.prototype = Object.create(ItemView.prototype);
|
||||
View.prototype.getViewType = function(){ return VIEW_TYPE; };
|
||||
|
|
@ -292,7 +333,7 @@ View.prototype.render = function(){
|
|||
var col = mk('div', 'ap-col');
|
||||
container.appendChild(col);
|
||||
|
||||
// Top: playlist dropdown
|
||||
// Top: playlist dropdown //
|
||||
var rowTop = mk('div', 'ap-row');
|
||||
col.appendChild(rowTop);
|
||||
var sel = mk('select');
|
||||
|
|
@ -300,13 +341,76 @@ View.prototype.render = function(){
|
|||
fillPlaylistOptions(this.plugin, sel); if (this.plugin.currentPlaylist>=0) sel.value = String(this.plugin.currentPlaylist);
|
||||
sel.onchange = function(){ if (sel.value!=='-1') this.plugin.setCurrentPlaylist(Number(sel.value)); }.bind(this);
|
||||
|
||||
// Now playing title
|
||||
// Now playing title //
|
||||
var title = mk('div', 'ap-title-large', this.plugin.currentTrackLabel());
|
||||
col.appendChild(title);
|
||||
|
||||
// Controls (with appended icons)
|
||||
var controls = mk('div', 'ap-row ap-controls');
|
||||
col.appendChild(controls);
|
||||
// Embedded player for link-based playlists //
|
||||
var cp = this.plugin.currentPlaylist;
|
||||
var cfgp = (cp>=0) ? (this.plugin.settings.playlists[cp]||{}) : {};
|
||||
var activeEmbed = (cfgp && cfgp.type==='link') ? (this.plugin.embedByPlaylist && this.plugin.embedByPlaylist[cp] || null) : null;
|
||||
if (activeEmbed && activeEmbed.src){
|
||||
|
||||
// APL_FORCE_SC_IFRAME - SoundCloud injection (track height fix) - was sensitive //
|
||||
try{
|
||||
if (activeEmbed && activeEmbed.provider === 'soundcloud'){
|
||||
var _raw = (activeEmbed.original || activeEmbed.src || (cfgp && cfgp.link) || '');
|
||||
var _clean = (_raw||'').split('#')[0].split('?')[0];
|
||||
var _isSet = _clean.indexOf('/sets/') !== -1;
|
||||
var targetHeight = _isSet ? 600 : 420; // ensures large UI for tracks
|
||||
|
||||
// Dedicated container and iframe //
|
||||
var _wrap = mk('div','ap-embedbox');
|
||||
var _ifr = document.createElement('iframe');
|
||||
_ifr.width = '100%';
|
||||
_ifr.height = String(targetHeight);
|
||||
_ifr.frameBorder = '0';
|
||||
_ifr.scrolling = 'no';
|
||||
_ifr.allow = 'autoplay; clipboard-write; encrypted-media; picture-in-picture';
|
||||
_ifr.allowFullscreen = true;
|
||||
_ifr.src = 'https://w.soundcloud.com/player/?visual=true&url=' + encodeURIComponent(_clean) + '&show_artwork=true';
|
||||
|
||||
// Insert at top of column and stop further renders //
|
||||
col.appendChild(_wrap);
|
||||
_wrap.appendChild(_ifr);
|
||||
return;
|
||||
}
|
||||
}catch(e){}
|
||||
var wrap = mk('div','ap-embedbox');
|
||||
var iframe = wrap.createEl('iframe', { attr: {
|
||||
src: activeEmbed.src,
|
||||
width: '100%',
|
||||
height: String(activeEmbed.height||380),
|
||||
frameborder: '0',
|
||||
allow: 'autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share',
|
||||
allowfullscreen: 'true',
|
||||
scrolling: 'no'
|
||||
}});
|
||||
col.appendChild(wrap);
|
||||
// Try oEmbed for SoundCloud - fallback to iframe
|
||||
try {
|
||||
if (activeEmbed && activeEmbed.provider==='soundcloud'){
|
||||
var _src0 = (activeEmbed && (activeEmbed.original||activeEmbed.src)) || (cfgp && cfgp.link) || '';
|
||||
fetchSoundCloudOEmbed(_src0, activeEmbed.height||450).then(function(html){
|
||||
if (html){ wrap.innerHTML = html; }
|
||||
});
|
||||
}
|
||||
} catch(e){}
|
||||
|
||||
try {
|
||||
var _openSrc = (activeEmbed && (activeEmbed.original || activeEmbed.src)) || (cfgp && cfgp.link) || '';
|
||||
var btnRow = mk('div','ap-row ap-embed-actions');
|
||||
var btn = mk('button','ap-open-ext ap-open-ext--mini','Optional: Open In Browser');
|
||||
btn.onclick = function(){ try{ window.open(_openSrc, '_blank'); }catch(e){} };
|
||||
btnRow.appendChild(btn);
|
||||
col.appendChild(btnRow);
|
||||
} catch(e){}
|
||||
|
||||
}
|
||||
|
||||
// Controls (hidden when an external embed is active) //
|
||||
var controls = null;
|
||||
if (!(activeEmbed && activeEmbed.src)) { controls = mk('div', 'ap-row ap-controls'); col.appendChild(controls); }
|
||||
var prev = mk('button', '', 'Prev ◀︎◀︎'); prev.title='Previous';
|
||||
var play = mk('button', '', (this.plugin.audio.paused ? 'Play ▶︎' : 'Pause Ⅱ')); play.title='Play/Pause';
|
||||
var next = mk('button', '', 'Skip ▶︎▶︎'); next.title='Next';
|
||||
|
|
@ -314,38 +418,36 @@ View.prototype.render = function(){
|
|||
var shuffle = mk('button', '', this.plugin.shuffle ? 'Shuffle (ON)' : 'Shuffle'); shuffle.title='Shuffle';
|
||||
var rptLabel = this.plugin.repeatMode === 'all' ? 'Repeat (All)' : (this.plugin.repeatMode === 'one' ? 'Repeat (One)' : 'Repeat');
|
||||
var repeatBtn = mk('button', '', rptLabel); repeatBtn.title='Repeat mode';
|
||||
controls.appendChild(prev); controls.appendChild(play); controls.appendChild(next); controls.appendChild(stop);
|
||||
controls.appendChild(shuffle); controls.appendChild(repeatBtn);
|
||||
if (controls) { controls.appendChild(prev); controls.appendChild(play); controls.appendChild(next); controls.appendChild(stop); controls.appendChild(shuffle); controls.appendChild(repeatBtn); }
|
||||
|
||||
prev.onclick = function(){ this.plugin.prev(); }.bind(this);
|
||||
play.onclick = function(){ this.plugin.togglePlay(); play.textContent = (this.plugin.audio.paused ? 'Play ▶︎' : 'Pause Ⅱ'); }.bind(this);
|
||||
next.onclick = function(){ this.plugin.next(); }.bind(this);
|
||||
stop.onclick = function(){ if (this.plugin.settings.fadeOnStop) this.plugin.stopFade(this.plugin.settings.fadeMs||3000); else this.plugin.stop(); }.bind(this);
|
||||
if (this.plugin.shuffle) shuffle.classList.add('is-active');
|
||||
shuffle.onclick = function(){ this.plugin.toggleShuffle(); shuffle.textContent = this.plugin.shuffle ? 'Shuffle (ON)' : 'Shuffle'; shuffle.classList.toggle('is-active', this.plugin.shuffle); }.bind(this);
|
||||
repeatBtn.classList.toggle('is-active', this.plugin.repeatMode !== 'off');
|
||||
repeatBtn.onclick = function(){ this.plugin.cycleRepeat(); var lbl = this.plugin.repeatMode === 'all' ? 'Repeat (All)' : (this.plugin.repeatMode === 'one' ? 'Repeat (One)' : 'Repeat'); repeatBtn.textContent = lbl; repeatBtn.classList.toggle('is-active', this.plugin.repeatMode !== 'off'); }.bind(this);
|
||||
if (controls) prev.onclick = function(){ this.plugin.prev(); }.bind(this);
|
||||
if (controls) play.onclick = function(){ this.plugin.togglePlay(); play.textContent = (this.plugin.audio.paused ? 'Play ▶︎' : 'Pause Ⅱ'); }.bind(this);
|
||||
if (controls) next.onclick = function(){ this.plugin.next(); }.bind(this);
|
||||
if (controls) stop.onclick = function(){ if (this.plugin.settings.fadeOnStop) this.plugin.stopFade(this.plugin.settings.fadeMs||3000); else this.plugin.stop(); }.bind(this);
|
||||
if (controls && this.plugin.shuffle) shuffle.classList.add('is-active');
|
||||
if (controls) shuffle.onclick = function(){ this.plugin.toggleShuffle(); shuffle.textContent = this.plugin.shuffle ? 'Shuffle (ON)' : 'Shuffle'; shuffle.classList.toggle('is-active', this.plugin.shuffle); }.bind(this);
|
||||
if (controls) repeatBtn.classList.toggle('is-active', this.plugin.repeatMode !== 'off');
|
||||
if (controls) repeatBtn.onclick = function(){ this.plugin.cycleRepeat(); var lbl = this.plugin.repeatMode === 'all' ? 'Repeat (All)' : (this.plugin.repeatMode === 'one' ? 'Repeat (One)' : 'Repeat'); repeatBtn.textContent = lbl; repeatBtn.classList.toggle('is-active', this.plugin.repeatMode !== 'off'); }.bind(this);
|
||||
|
||||
// Seek
|
||||
var seekRow = mk('div', 'ap-row ap-seek');
|
||||
col.appendChild(seekRow);
|
||||
// Seek //
|
||||
var seekRow = null;
|
||||
if (!(activeEmbed && activeEmbed.src)) { seekRow = mk('div', 'ap-row ap-seek'); col.appendChild(seekRow); }
|
||||
var cur = mk('span', '', '0:00');
|
||||
var range = mk('input'); range.type='range'; range.min='0'; range.max='1000'; range.value='0';
|
||||
var dur = mk('span', '', '0:00');
|
||||
seekRow.appendChild(cur); seekRow.appendChild(range); seekRow.appendChild(dur);
|
||||
range.oninput = function(){ this.plugin.seekToFraction(Number(range.value)/1000); }.bind(this);
|
||||
if (seekRow) { seekRow.appendChild(cur); seekRow.appendChild(range); seekRow.appendChild(dur); }
|
||||
if (seekRow) range.oninput = function(){ this.plugin.seekToFraction(Number(range.value)/1000); }.bind(this);
|
||||
|
||||
// Volume (centered, wide)
|
||||
var volRow = mk('div', 'ap-row ap-vol-wide');
|
||||
col.appendChild(volRow);
|
||||
volRow.appendChild(mk('span','', 'Vol'));
|
||||
// Volume (centered, wide) //
|
||||
var volRow = null;
|
||||
if (!(activeEmbed && activeEmbed.src)) { volRow = mk('div', 'ap-row ap-vol-wide'); col.appendChild(volRow); if (volRow) volRow.appendChild(mk('span','', 'Vol')); }
|
||||
var vol = mk('input'); vol.type='range'; vol.min='0'; vol.max='100'; vol.value = String(Math.round(this.plugin.audio.volume*100));
|
||||
volRow.appendChild(vol);
|
||||
vol.oninput = function(){ this.plugin.setVolume(Number(vol.value)/100); }.bind(this);
|
||||
if (volRow) volRow.appendChild(vol);
|
||||
if (volRow) vol.oninput = function(){ this.plugin.setVolume(Number(vol.value)/100); }.bind(this);
|
||||
|
||||
// Track list (row click to play, Title — Artist, duration right, current with 🔊)
|
||||
var list = mk('div', 'audio-list');
|
||||
col.appendChild(list);
|
||||
// Track list (row click to play, Title - Artist, duration right, current with 🔊 icon) //
|
||||
var list = null;
|
||||
if (!(activeEmbed && activeEmbed.src)) { list = mk('div', 'audio-list'); col.appendChild(list); }
|
||||
|
||||
var showArtist = this.plugin.settings.showArtist;
|
||||
var showAlbum = this.plugin.settings.showAlbum;
|
||||
|
|
@ -364,19 +466,19 @@ View.prototype.render = function(){
|
|||
return cols.join(' ');
|
||||
}
|
||||
|
||||
// Header
|
||||
var header = mk('div','audio-header');
|
||||
header.style.gridTemplateColumns = gridCols();
|
||||
header.appendChild(mk('div','cell-icon',''));
|
||||
header.appendChild(mk('div','cell-title','Title'));
|
||||
if (showArtist) header.appendChild(mk('div','cell-artist','Artist'));
|
||||
if (showAlbum) header.appendChild(mk('div','cell-album','Album'));
|
||||
header.appendChild(mk('div','cell-time','Time'));
|
||||
list.appendChild(header);
|
||||
// Header //
|
||||
var header = list ? mk('div','audio-header') : null;
|
||||
if (header) header.style.gridTemplateColumns = gridCols();
|
||||
if (header) header.appendChild(mk('div','cell-icon',''));
|
||||
if (header) header.appendChild(mk('div','cell-title','Title'));
|
||||
if (header && showArtist) header.appendChild(mk('div','cell-artist','Artist'));
|
||||
if (header && showAlbum) header.appendChild(mk('div','cell-album','Album'));
|
||||
if (header) { header.appendChild(mk('div','cell-time','Time')); if (list) list.appendChild(header); }
|
||||
if (list) list.appendChild(header);
|
||||
|
||||
var tracks = this.plugin.getTracks();
|
||||
if (!tracks.length) list.appendChild(mk('div','', 'No audio files found for this playlist.'));
|
||||
for (var i=0;i<tracks.length;i++){
|
||||
if (list && !tracks.length) list.appendChild(mk('div','', 'No audio files found for this playlist.'));
|
||||
for (var i=0;i<tracks.length;i++){ if (!list) break;
|
||||
(function(i, t, self){
|
||||
var row = mk('div', 'audio-row');
|
||||
row.style.gridTemplateColumns = gridColsFor(self.plugin.index===i);
|
||||
|
|
@ -391,7 +493,7 @@ View.prototype.render = function(){
|
|||
var right = mk('div','cell-time', t.duration!=null ? fmtTime(t.duration) : '--:--');
|
||||
row.appendChild(right);
|
||||
row.onclick = function(){ self.plugin.playIndex(i); };
|
||||
list.appendChild(row);
|
||||
if (list) list.appendChild(row);
|
||||
})(i, tracks[i], this);
|
||||
}
|
||||
|
||||
|
|
@ -410,7 +512,7 @@ View.prototype.render = function(){
|
|||
this.plugin.onUiRefresh = function(){ self.render(); };
|
||||
};
|
||||
|
||||
/* Main plugin */
|
||||
// Main plugin //
|
||||
module.exports = class AudioPlugList extends Plugin {
|
||||
async onload() {
|
||||
this.settings = Object.assign({
|
||||
|
|
@ -425,17 +527,18 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
}, await this.loadData());
|
||||
|
||||
this.tracksByPlaylist = Array.from({length: this.settings.playlists.length}, ()=>[]);
|
||||
this.embedByPlaylist = Array.from({length: this.settings.playlists.length}, ()=>null);
|
||||
this.currentPlaylist = (this.settings.playlists.length ? clamp(this.settings.currentPlaylist, 0, this.settings.playlists.length-1) : -1);
|
||||
this.queue = [];
|
||||
this.index = -1;
|
||||
this.audio = new Audio();
|
||||
this.shuffle = true;
|
||||
this.repeatMode = 'all'; // default 'all'
|
||||
this.shuffle = true; // default 'true' by request //
|
||||
this.repeatMode = 'all'; // default 'all' by request //
|
||||
this.onUiRefresh = null;
|
||||
|
||||
this.audio.addEventListener('ended', this._onEnded.bind(this));
|
||||
|
||||
// Ribbon icon
|
||||
// Ribbon icon //
|
||||
this.addRibbonIcon('music', 'Audio PlugList', () => this.activateView());
|
||||
|
||||
this.registerView(VIEW_TYPE, (leaf) => new View(leaf, this));
|
||||
|
|
@ -477,7 +580,7 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
}
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
// Footer //
|
||||
_attachFooter(){
|
||||
if (this.footerEl) return;
|
||||
var root = document.body.querySelector('.app-container, .mod-root') || document.body;
|
||||
|
|
@ -487,7 +590,7 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
fillPlaylistOptions(this, sel); if (this.currentPlaylist>=0) sel.value = String(this.currentPlaylist);
|
||||
sel.onchange = function(){ if (sel.value!=='-1') this.setCurrentPlaylist(Number(sel.value)); }.bind(this);
|
||||
|
||||
// Segmented controls ⏮ | ⏯ | ⏭ | ⏹ | 🔀 | 🔁
|
||||
// Segmented controls ⏮ | ⏯ | ⏭ | ⏹ | 🔀 | 🔁 - some of these had to be done the way that are. Switched from 1.0.3 due to mobile and device view issues so below ones are different but working. //
|
||||
var controls = el.createEl('div', { cls:'ap-footer-group' });
|
||||
var bPrev = controls.createEl('button', { cls:'ap-footer-seg no-active plain', text:'◀︎◀︎', title:'Previous' }); bPrev.onclick = ()=>this.prev();
|
||||
var bPlay = controls.createEl('button', { cls:'ap-footer-seg', text:(this.audio.paused?'▶':'Ⅱ'), title:'Play/Pause' }); bPlay.onclick = ()=>{ this.togglePlay(); bPlay.textContent = this.audio.paused ? '▶' : 'Ⅱ'; };
|
||||
|
|
@ -520,7 +623,6 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
root.appendChild(el);
|
||||
this.footerEl = el;
|
||||
|
||||
// initialize active states
|
||||
// init active states only for shuffle/repeat
|
||||
if (this.shuffle) bShuf.classList.add('is-active');
|
||||
if (this.repeatMode !== 'off') bRpt.classList.add('is-active');
|
||||
|
|
@ -572,21 +674,61 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
if (typeof this.onUiRefresh === 'function') this.onUiRefresh();
|
||||
}
|
||||
|
||||
/* Indexing */
|
||||
// Indexing //
|
||||
async indexAll(){ for (var i=0;i<this.settings.playlists.length;i++) await this.indexPlaylist(i); this._applyPlaylistQueue(); }
|
||||
async indexPlaylist(i){
|
||||
var cfg = this.settings.playlists[i] || {};
|
||||
// LINK-based playlist support
|
||||
// LINK-based playlist support //
|
||||
if (cfg.type === 'link'){
|
||||
var link = (cfg.link||'').trim();
|
||||
var name = (cfg.name||link||('Playlist '+(i+1)));
|
||||
if (!link){ this.tracksByPlaylist[i]=[]; new Notice('Audio PlugList: link missing for "'+name+'".'); return; }
|
||||
var t = { id: link, path: link, title: name, meta: { artist:'', title:name, album:'' }, duration: null };
|
||||
this.tracksByPlaylist[i] = [t];
|
||||
this._preloadDurations(i);
|
||||
if (!link){ this.tracksByPlaylist[i]=[]; if (this.embedByPlaylist) this.embedByPlaylist[i]=null; new Notice('Audio PlugList: link missing for \"'+name+'\".'); return; }
|
||||
|
||||
if (!this.embedByPlaylist) this.embedByPlaylist = Array.from({length: this.settings.playlists.length}, ()=>null);
|
||||
|
||||
// YouTube playlist (iframe embed) //
|
||||
var ytid = getYouTubePlaylistId(link);
|
||||
if (ytid){
|
||||
this.tracksByPlaylist[i] = [];
|
||||
this.embedByPlaylist[i] = { provider:'youtube', src: ytPlaylistEmbedSrc(ytid), height: 380, original: link };
|
||||
new Notice('Audio PlugList: embedded YouTube playlist.');
|
||||
return;
|
||||
}
|
||||
|
||||
// SoundCloud (track or set) //
|
||||
if (isSoundCloudUrl(link)){
|
||||
this.tracksByPlaylist[i] = [];
|
||||
var _isSet = isSoundCloudSet(link);
|
||||
this.embedByPlaylist[i] = { provider:'soundcloud', src: scEmbedSrc(link, _isSet), height: (_isSet?600:166) };
|
||||
new Notice('Audio PlugList: embedded SoundCloud.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Spotify playlist (embed; playback may be short previews without prem) //
|
||||
var spid = getSpotifyPlaylistId(link);
|
||||
if (spid){
|
||||
this.tracksByPlaylist[i] = [];
|
||||
this.embedByPlaylist[i] = { provider:'spotify', src: spotifyEmbedSrc(spid), height: 380, original: link };
|
||||
new Notice('Audio PlugList: embedded Spotify playlist.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Direct audio URL //
|
||||
if (isDirectAudioUrl(link)){
|
||||
var t = { id: link, path: link, title: name, meta: { artist:'', title:name, album:'' }, duration: null };
|
||||
this.tracksByPlaylist[i] = [t];
|
||||
this.embedByPlaylist[i] = null;
|
||||
this._preloadDurations(i);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: not embeddable //
|
||||
this.tracksByPlaylist[i] = [];
|
||||
this.embedByPlaylist[i] = { provider:'external', src: link, height: 0, openExternally:true };
|
||||
new Notice('Audio PlugList: link is not embeddable or a direct audio file.');
|
||||
return;
|
||||
}
|
||||
// FOLDER-based (default)
|
||||
// FOLDER-based (default) //
|
||||
var cfg = this.settings.playlists[i] || { folderPath:'' };
|
||||
var fp = (cfg.folderPath||'').trim();
|
||||
var root = fp ? this.app.vault.getAbstractFileByPath(fp) : this.app.vault.getRoot();
|
||||
|
|
@ -618,7 +760,7 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
if (t.duration != null) return;
|
||||
var a = new Audio();
|
||||
a.preload = 'metadata';
|
||||
a.src = self.app.vault.adapter.getResourcePath(t.path);
|
||||
a.src = /^https?:\/\//i.test(t.path) ? t.path : self.app.vault.adapter.getResourcePath(t.path);
|
||||
a.addEventListener('loadedmetadata', function(){
|
||||
t.duration = a.duration || 0;
|
||||
if (typeof self.onUiRefresh === 'function') self.onUiRefresh();
|
||||
|
|
@ -636,12 +778,27 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
return true;
|
||||
}
|
||||
playIndex(i){ if (i<0 || i>=this.queue.length) return; this.index = i; if (this.loadCurrent()) this.audio.play(); this._notifyUi(); }
|
||||
togglePlay(){
|
||||
|
||||
_activeEmbed(){
|
||||
try {
|
||||
var cp = this.currentPlaylist;
|
||||
var cfg = (cp>=0) ? (this.settings.playlists[cp]||{}) : null;
|
||||
if (!cfg || cfg.type!=='link') return null;
|
||||
return (this.embedByPlaylist && this.embedByPlaylist[cp]) || null;
|
||||
} catch(e){ return null; }
|
||||
}
|
||||
togglePlay(){
|
||||
var __em = this._activeEmbed && this._activeEmbed();
|
||||
if (__em && __em.src){ new Notice('This playlist uses an embedded player. Use the player in the tab.'); if (this.activateView) this.activateView(); return; }
|
||||
|
||||
if (!this.current() && this.queue.length){ this.index = 0; if (!this.loadCurrent()) return; }
|
||||
if (this.audio.paused) this.audio.play(); else this.audio.pause();
|
||||
this._notifyUi();
|
||||
}
|
||||
stop(){ this.audio.pause(); this.audio.currentTime = 0; this._notifyUi(); }
|
||||
stop(){
|
||||
var __em = this._activeEmbed && this._activeEmbed();
|
||||
if (__em && __em.src){ new Notice('This playlist uses an embedded player. Use the player in the tab.'); if (this.activateView) this.activateView(); return; }
|
||||
this.audio.pause(); this.audio.currentTime = 0; this._notifyUi(); }
|
||||
stopFade(durationMs){
|
||||
durationMs = durationMs || 3000;
|
||||
var a = this.audio;
|
||||
|
|
@ -663,6 +820,9 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
}, Math.max(10, Math.floor(durationMs/steps)));
|
||||
}
|
||||
next(){
|
||||
var __em = this._activeEmbed && this._activeEmbed();
|
||||
if (__em && __em.src){ new Notice('This playlist uses an embedded player. Use the player in the tab.'); if (this.activateView) this.activateView(); return; }
|
||||
|
||||
if (this.repeatMode === 'one' && this.current()){ this.audio.currentTime = 0; this.audio.play(); this._notifyUi(); return; }
|
||||
this.index = this._pickNextIndex(this.index);
|
||||
if (this.index === -1){
|
||||
|
|
@ -673,6 +833,9 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
this._notifyUi();
|
||||
}
|
||||
prev(){
|
||||
var __em = this._activeEmbed && this._activeEmbed();
|
||||
if (__em && __em.src){ new Notice('This playlist uses an embedded player. Use the player in the tab.'); if (this.activateView) this.activateView(); return; }
|
||||
|
||||
if (this.index > 0) this.index--; else this.audio.currentTime = 0;
|
||||
if (this.loadCurrent()) this.audio.play();
|
||||
this._notifyUi();
|
||||
|
|
@ -705,7 +868,7 @@ module.exports = class AudioPlugList extends Plugin {
|
|||
};
|
||||
function buildLabel(plugin, t){
|
||||
var meta = t.meta || {};
|
||||
var parts = [t.title]; // always start from the file basename
|
||||
var parts = [t.title]; // always start from the file basename //
|
||||
if (plugin && plugin.settings){
|
||||
if (plugin.settings.showArtist && meta.artist) parts.push(meta.artist);
|
||||
if (plugin.settings.showAlbum && meta.album) parts.push(meta.album);
|
||||
|
|
@ -713,3 +876,60 @@ function buildLabel(plugin, t){
|
|||
return parts.join(' — ');
|
||||
}
|
||||
|
||||
// APL SoundCloud guard: force widget URL + height //
|
||||
(function(){
|
||||
var fixedFlag = 'data-apl-sc-fixed';
|
||||
function toWidgetUrl(pageUrl){
|
||||
try {
|
||||
var clean = String(pageUrl||'').split('#')[0].split('?')[0];
|
||||
return 'https://w.soundcloud.com/player/?visual=true&url=' + encodeURIComponent(clean) + '&show_artwork=true';
|
||||
} catch(e){ return pageUrl; }
|
||||
}
|
||||
function fixIn(root){
|
||||
if (!root) return;
|
||||
var frames = root.querySelectorAll('iframe[src*="://soundcloud.com/"]:not(['+fixedFlag+'])');
|
||||
frames.forEach(function(ifr){
|
||||
try {
|
||||
var w = toWidgetUrl(ifr.src);
|
||||
if (w && w.indexOf('w.soundcloud.com/player') !== -1) {
|
||||
ifr.setAttribute('src', w);
|
||||
}
|
||||
var h = parseInt(ifr.getAttribute('height') || '0', 10);
|
||||
if (!h || h < 200) ifr.setAttribute('height', '420');
|
||||
ifr.setAttribute('allow', 'autoplay; clipboard-write; encrypted-media; picture-in-picture');
|
||||
ifr.setAttribute(fixedFlag, '1');
|
||||
} catch(_){}
|
||||
});
|
||||
}
|
||||
function activeViewContent(){
|
||||
return document.querySelector('.workspace-leaf.mod-active .view-content');
|
||||
}
|
||||
function kick(){
|
||||
var vc = activeViewContent();
|
||||
if (vc) fixIn(vc);
|
||||
}
|
||||
// initial nudges //
|
||||
var count = 0;
|
||||
var timer = setInterval(function(){
|
||||
kick();
|
||||
if (++count > 15) clearInterval(timer); // ~12s
|
||||
}, 800);
|
||||
|
||||
// observe future DOM changes (lightweight filter) //
|
||||
var obs = new MutationObserver(function(muts){
|
||||
for (var i=0;i<muts.length;i++){
|
||||
var n = muts[i].target;
|
||||
if (!n) continue;
|
||||
// only re-scan iframes are being added or attributes change //
|
||||
if (muts[i].type === 'childList' || muts[i].type === 'attributes') {
|
||||
kick();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
obs.observe(document.body, { childList:true, subtree:true, attributes:true, attributeFilter:['src','height'] });
|
||||
} catch(_){}
|
||||
})();
|
||||
|
||||
// I know i don't have to end with "//" but i'm slightly color blind so it helps me when my eyes are tired. //
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"id": "audio-pluglist",
|
||||
"name": "Audio PlugList",
|
||||
"version": "1.0.3",
|
||||
"minAppVersion": "1.8.0",
|
||||
"description": "Playlist-based audio player — folder & link playlists, mini-player, shuffle, repeat, fade-out.",
|
||||
"author": "ragetrip",
|
||||
"version": "1.2.5",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Create playlists from folders, files, or links (YouTube/SoundCloud/Spotify/direct audio). Tab view + footer controls.",
|
||||
"author": "Ragetrip",
|
||||
"authorUrl": "https://github.com/ragetrip",
|
||||
"fundingUrl": "https://buymeacoffee.com/ragetrip",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
}
|
||||
61
styles.css
61
styles.css
|
|
@ -96,7 +96,7 @@
|
|||
color: var(--text-normal) !important;
|
||||
}
|
||||
|
||||
/* --- Footer plain icons (Prev/Skip/Stop) stronger rules --- */
|
||||
/* Footer plain icons (Prev/Skip/Stop) stronger rules */
|
||||
.ap-footer-player .ap-footer-group .ap-footer-seg.plain,
|
||||
.ap-footer-player .ap-footer-group .ap-footer-seg.plain:hover,
|
||||
.ap-footer-player .ap-footer-group .ap-footer-seg.plain.is-active{
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* === Tab "table" layout (1.0.13b) === */
|
||||
/* Tab "table" layout (from 1.0.13b) */
|
||||
.audio-header, .audio-row {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
|
|
@ -131,7 +131,7 @@
|
|||
.audio-row .cell-time { text-align: right; opacity: .8; }
|
||||
.audio-row .cell-icon { width: 16px; text-align: center; }
|
||||
|
||||
/* === 1.0.13d: subtler zebra + stronger left alignment === */
|
||||
/* 1.0.13d: subtler zebra + stronger left alignment */
|
||||
/* Collapse icon column for non-playing rows so they sit further left - pita to get this right */
|
||||
.audio-row .cell-icon { width: 0; }
|
||||
.audio-row.is-playing .cell-icon { width: 16px; } /* keep space only for the playing row */
|
||||
|
|
@ -145,7 +145,7 @@
|
|||
.audio-list .audio-row:nth-child(odd){ background: transparent; }
|
||||
|
||||
|
||||
/* === Audio PlugList Settings spacing (dynamic playlists) === */
|
||||
/* Audio PlugList Settings spacing (dynamic playlists) */
|
||||
.apl-settings-addwrap{
|
||||
margin-bottom: 12px;
|
||||
padding: 10px;
|
||||
|
|
@ -164,7 +164,7 @@
|
|||
}
|
||||
|
||||
|
||||
/* === Settings: Playlist Card - box shadow was glitchy === */
|
||||
/* Settings: Playlist Card - box shadow was glitchy */
|
||||
.apl-card{
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 10px;
|
||||
|
|
@ -176,3 +176,54 @@
|
|||
.apl-card .setting-item-heading{
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
/* Mini external-open button */
|
||||
.ap-embed-actions { margin: 6px 0 12px; display: flex; gap: 8px; }
|
||||
.ap-open-ext { padding: 6px 10px; border: 1px solid var(--background-modifier-border); border-radius: 6px; cursor: pointer; background: var(--background-secondary); }
|
||||
.ap-open-ext--mini {
|
||||
font-size: 0.7em;
|
||||
padding: 3px 6px;
|
||||
line-height: 1.1;
|
||||
color: var(--text-accent);
|
||||
text-decoration: underline;
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
.ap-open-ext--mini:hover { text-decoration: underline; color: #3b82f6; }
|
||||
|
||||
/* Diagnostics panel (minimal) */
|
||||
.ap-diag { margin-top: 8px; }
|
||||
.ap-diag-toggle { font-size: 0.8em; padding: 2px 8px; border: 1px solid var(--background-modifier-border); border-radius: 6px; background: var(--background-secondary); cursor: pointer; }
|
||||
.ap-diag-toggle:hover { background: var(--background-modifier-hover); }
|
||||
.ap-diag-panel { margin-top: 6px; padding: 6px; border: 1px dashed var(--background-modifier-border); border-radius: 6px; font-size: 0.85em; }
|
||||
.ap-diag-line { margin: 2px 0; word-break: break-all; }
|
||||
|
||||
.ap-embedbox{min-height:180px}
|
||||
.ap-embedbox iframe{display:block;width:100%;height:100%}
|
||||
|
||||
/* Make repeat pill match shuffle pill */
|
||||
.ap-footer .ap-btn-repeat,
|
||||
.ap-footer .ap-btn-shuffle {
|
||||
border-radius: 9999px;
|
||||
padding: 2px 10px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
height: 24px;
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
}
|
||||
.ap-footer .ap-btn-repeat.is-active,
|
||||
.ap-footer .ap-btn-shuffle.is-active {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
.ap-footer .ap-btn-repeat .icon,
|
||||
.ap-footer .ap-btn-shuffle .icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
/* APL footer: Better alignment for the repeat button that carries over to smaller hardware/devices */
|
||||
.ap-footer-player .ap-footer-group button[title^="Repeat"] {
|
||||
padding-left: 4px !important;
|
||||
padding-right: 16px !important;
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
{
|
||||
"1.0.3": "1.8.0"
|
||||
}
|
||||
"1.2.5": "1.5.0"
|
||||
}
|
||||
Loading…
Reference in a new issue