diff --git a/APLAYER_INTEGRATION.md b/APLAYER_INTEGRATION.md new file mode 100644 index 000000000..58e4cb739 --- /dev/null +++ b/APLAYER_INTEGRATION.md @@ -0,0 +1,114 @@ +# APlayer Integration for Navidrome Shares + +This integration allows you to share music from Navidrome using APlayer, a beautiful HTML5 music player, without requiring authentication. + +## Features + +- 🎵 Beautiful, responsive music player interface +- 🔐 No authentication required - works with public share links +- ⏰ Respects share expiration dates +- 🎨 Clean, modern design +- 📱 Mobile-friendly +- 🔗 Easy to embed on external websites + +## How to Use + +### 1. Create a Share in Navidrome + +1. In Navidrome, select songs, albums, or playlists you want to share +2. Click the share button and create a share link +3. Configure the share settings (expiration, description, etc.) + +### 2. Get the APlayer URL + +1. Go to the Navidrome admin panel +2. Navigate to "Shares" in the menu +3. Click on your share to edit it +4. You'll see two URLs: + - **Share URL**: The regular Navidrome share page + - **APlayer Embed URL**: The APlayer player page + +### 3. Share or Embed + +You can either: + +- **Direct link**: Share the APlayer URL directly for people to listen in their browser +- **Embed in website**: Use an iframe to embed the player on your own website + +#### Embed Example + +```html + +``` + +## Technical Details + +### How It Works + +1. The APlayer page loads the share data from the server (no authentication needed) +2. Track streaming uses JWT tokens embedded in the share link +3. Tokens automatically expire when the share expires +4. All streaming is done through Navidrome's public API endpoints + +### Security + +- No username/password required +- Uses the same security model as regular Navidrome shares +- JWT tokens are scoped to specific shares +- Respects share expiration dates +- Cannot access data outside the shared content + +### Files Added/Modified + +**New Files:** +- `resources/aplayer.html` - HTML template for the APlayer page +- `resources/aplayer-share.js` - JavaScript that initializes APlayer with share data + +**Modified Files:** +- `server/public/public.go` - Added route for `/share/:id/aplayer` +- `server/public/handle_shares.go` - Added handler for APlayer page +- `ui/src/utils/urls.js` - Added `shareAPlayerUrl()` function +- `ui/src/share/ShareEdit.jsx` - Added APlayer URL display + +## Customization + +### Styling + +You can customize the appearance by modifying `resources/aplayer.html`. The default theme uses a purple gradient background, but you can change: + +- Colors and gradients +- Player theme color +- Layout and spacing +- Font styles + +### Player Options + +Edit `resources/aplayer-share.js` to modify APlayer settings: + +```javascript +const ap = new APlayer({ + autoplay: false, // Auto-start playback + theme: '#b7daff', // Player color theme + loop: 'all', // Loop mode (all/one/none) + volume: 0.7, // Default volume (0-1) + // ... more options +}); +``` + +For all available options, see [APlayer documentation](https://aplayer.js.org/). + +## Credits + +- [Navidrome](https://github.com/navidrome/navidrome) - Modern Music Server +- [APlayer](https://github.com/DIYgod/APlayer) - Beautiful HTML5 Music Player +- [AplayerForNavidrome](https://github.com/maytom2016/AplayerForNavidrome) - Original inspiration + +## License + +This integration follows the same license as Navidrome (GPL-3.0). diff --git a/go.mod b/go.mod index 8b741caff..478f81263 100644 --- a/go.mod +++ b/go.mod @@ -125,6 +125,7 @@ require ( github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/wtolson/go-taglib v0.0.0-20210406152913-79209c280058 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect go.uber.org/multierr v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect diff --git a/go.sum b/go.sum index f5916c912..d85baf346 100644 --- a/go.sum +++ b/go.sum @@ -277,6 +277,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/unrolled/secure v1.17.0 h1:Io7ifFgo99Bnh0J7+Q+qcMzWM6kaDPCA5FroFZEdbWU= github.com/unrolled/secure v1.17.0/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40= +github.com/wtolson/go-taglib v0.0.0-20210406152913-79209c280058 h1:/kj9W8wSHTlwt/i4n6902i/YOPYNIXiDR/PAmgbrDyc= +github.com/wtolson/go-taglib v0.0.0-20210406152913-79209c280058/go.mod h1:p+WHGfN/a+Ol37Pm7EIOO/6Cylieb2qn1jmKfxtSsUg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg= github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/resources/aplayer-share.js b/resources/aplayer-share.js new file mode 100644 index 000000000..d94e08fa2 --- /dev/null +++ b/resources/aplayer-share.js @@ -0,0 +1,96 @@ +/** + * APlayer integration for Navidrome Share Links + * Works with public share links without authentication + */ +(function() { + 'use strict'; + + // Wait for DOM and APlayer to be ready + function initAPlayer() { + if (typeof APlayer === 'undefined') { + console.error('APlayer library not loaded'); + return; + } + + // Get share info from the page (injected by server) + const shareInfoElement = document.getElementById('share-info'); + if (!shareInfoElement) { + console.error('Share info not found'); + return; + } + + let shareInfo; + try { + shareInfo = JSON.parse(shareInfoElement.textContent); + } catch (e) { + console.error('Failed to parse share info:', e); + return; + } + + if (!shareInfo || !shareInfo.tracks || shareInfo.tracks.length === 0) { + console.error('No tracks found in share'); + return; + } + + // Get base URL from the page + const baseURL = window.NavidromeConfig?.baseURL || ''; + + // Convert share tracks to APlayer format + const playlist = shareInfo.tracks.map(function(track) { + // Stream URL uses the encoded track ID (contains JWT token) + const streamUrl = baseURL + '/public/s/' + track.id; + + // Cover art URL - we'll construct it from the share's image + const coverUrl = shareInfo.imageUrl || baseURL + '/android-chrome-192x192.png'; + + return { + name: track.title || 'Unknown Title', + artist: track.artist || 'Unknown Artist', + url: streamUrl, + cover: coverUrl, + theme: '#b7daff' + }; + }); + + // Initialize APlayer + const container = document.getElementById('aplayer'); + if (!container) { + console.error('APlayer container not found'); + return; + } + + const ap = new APlayer({ + container: container, + lrcType: 0, + audio: playlist, + autoplay: false, + theme: '#b7daff', + loop: 'all', + order: 'list', + preload: 'auto', + volume: 0.7, + mutex: true, + listFolded: false, + listMaxHeight: 90, + }); + + // Log initialization + console.log('APlayer initialized with', playlist.length, 'tracks'); + + // Optional: Add event listeners + ap.on('play', function() { + console.log('Playing:', ap.list.audios[ap.list.index].name); + }); + + ap.on('error', function() { + console.error('Playback error'); + }); + } + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initAPlayer); + } else { + initAPlayer(); + } +})(); diff --git a/resources/aplayer.html b/resources/aplayer.html new file mode 100644 index 000000000..8f5272a59 --- /dev/null +++ b/resources/aplayer.html @@ -0,0 +1,128 @@ + + +
+ + +Shared Music Player
+