mirror of
https://github.com/navidrome/navidrome.git
synced 2026-05-03 06:51:16 +00:00
add aplayer for shared url support
# 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 <iframe src="http://your-navidrome-server/share/SHARE_ID/aplayer" width="100%" height="500" frameborder="0" allow="autoplay"> </iframe> ``` ## 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).
This commit is contained in:
parent
c7ac0e4414
commit
605902c6c0
114
APLAYER_INTEGRATION.md
Normal file
114
APLAYER_INTEGRATION.md
Normal file
@ -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
|
||||
<iframe
|
||||
src="http://your-navidrome-server/share/SHARE_ID/aplayer"
|
||||
width="100%"
|
||||
height="500"
|
||||
frameborder="0"
|
||||
allow="autoplay">
|
||||
</iframe>
|
||||
```
|
||||
|
||||
## 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).
|
||||
1
go.mod
1
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
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
96
resources/aplayer-share.js
Normal file
96
resources/aplayer-share.js
Normal file
@ -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();
|
||||
}
|
||||
})();
|
||||
128
resources/aplayer.html
Normal file
128
resources/aplayer.html
Normal file
@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{.ShareDescription}} - Navidrome</title>
|
||||
<meta name="description" content="Shared music player - {{.ShareDescription}}">
|
||||
|
||||
<!-- APlayer CSS -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.css">
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.player-wrapper {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#aplayer {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Make APlayer responsive */
|
||||
@media (max-width: 600px) {
|
||||
.header h1 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>{{.ShareDescription}}</h1>
|
||||
<p>Shared Music Player</p>
|
||||
</div>
|
||||
|
||||
<div class="player-wrapper">
|
||||
<div id="aplayer"></div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
Powered by <a href="https://github.com/navidrome/navidrome" target="_blank" rel="noopener noreferrer">Navidrome</a>
|
||||
& <a href="https://github.com/DIYgod/APlayer" target="_blank" rel="noopener noreferrer">APlayer</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Share info (injected by server) -->
|
||||
<script id="share-info" type="application/json">{{.ShareInfo}}</script>
|
||||
|
||||
<!-- Navidrome config (for baseURL) -->
|
||||
<script>
|
||||
window.NavidromeConfig = {
|
||||
baseURL: "{{.BaseURL}}"
|
||||
};
|
||||
</script>
|
||||
|
||||
<!-- APlayer library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/aplayer@1.10.1/dist/APlayer.min.js"></script>
|
||||
|
||||
<!-- APlayer Share integration script -->
|
||||
<script>{{.APlayerScript}}</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -2,16 +2,23 @@ package public
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/resources"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
"github.com/navidrome/navidrome/ui"
|
||||
"github.com/navidrome/navidrome/utils/req"
|
||||
"github.com/navidrome/navidrome/utils/slice"
|
||||
"github.com/navidrome/navidrome/utils/str"
|
||||
)
|
||||
|
||||
func (pub *Router) handleShares(w http.ResponseWriter, r *http.Request) {
|
||||
@ -59,6 +66,144 @@ func (pub *Router) handleM3U(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(s.ToM3U8()))
|
||||
}
|
||||
|
||||
func (pub *Router) handleAPlayer(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := req.Params(r).String(":id")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Load share
|
||||
s, err := pub.share.Load(r.Context(), id)
|
||||
if err != nil {
|
||||
checkShareError(r.Context(), w, err, id)
|
||||
return
|
||||
}
|
||||
|
||||
// Map share info for APlayer
|
||||
s = pub.mapShareInfo(r, *s)
|
||||
|
||||
// Read template
|
||||
tmplData, err := resources.FS().Open("aplayer.html")
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Could not find aplayer.html template", err)
|
||||
http.Error(w, "Template not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer tmplData.Close()
|
||||
|
||||
tmplContent := make([]byte, 0)
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := tmplData.Read(buf)
|
||||
if n > 0 {
|
||||
tmplContent = append(tmplContent, buf[:n]...)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Read APlayer script
|
||||
scriptData, err := resources.FS().Open("aplayer-share.js")
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Could not find aplayer-share.js", err)
|
||||
http.Error(w, "Script not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer scriptData.Close()
|
||||
|
||||
scriptContent := make([]byte, 0)
|
||||
for {
|
||||
n, err := scriptData.Read(buf)
|
||||
if n > 0 {
|
||||
scriptContent = append(scriptContent, buf[:n]...)
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Parse template
|
||||
tmpl, err := template.New("aplayer").Parse(string(tmplContent))
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Error parsing aplayer.html template", err)
|
||||
http.Error(w, "Error parsing template", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare share data for JSON
|
||||
type aplayerTrack struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Artist string `json:"artist"`
|
||||
Album string `json:"album"`
|
||||
Duration float32 `json:"duration"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type aplayerShareInfo struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Downloadable bool `json:"downloadable"`
|
||||
Tracks []aplayerTrack `json:"tracks"`
|
||||
ImageUrl string `json:"imageUrl"`
|
||||
}
|
||||
|
||||
shareData := aplayerShareInfo{
|
||||
ID: s.ID,
|
||||
Description: s.Description,
|
||||
Downloadable: s.Downloadable,
|
||||
ImageUrl: s.ImageURL,
|
||||
Tracks: slice.Map(s.Tracks, func(mf model.MediaFile) aplayerTrack {
|
||||
return aplayerTrack{
|
||||
ID: mf.ID,
|
||||
Title: mf.Title,
|
||||
Artist: mf.Artist,
|
||||
Album: mf.Album,
|
||||
Duration: mf.Duration,
|
||||
UpdatedAt: mf.UpdatedAt,
|
||||
}
|
||||
}),
|
||||
}
|
||||
|
||||
shareInfoJSON, err := json.Marshal(shareData)
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Error converting share data to JSON", err)
|
||||
http.Error(w, "Error processing share data", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare template data
|
||||
description := s.Description
|
||||
if description == "" {
|
||||
description = str.SanitizeText(s.Contents)
|
||||
}
|
||||
if description == "" {
|
||||
description = "Shared Music"
|
||||
}
|
||||
|
||||
baseURL := str.SanitizeText(conf.Server.BasePath)
|
||||
if baseURL == "" {
|
||||
baseURL = ""
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"ShareDescription": description,
|
||||
"ShareInfo": string(shareInfoJSON),
|
||||
"APlayerScript": string(scriptContent),
|
||||
"BaseURL": baseURL,
|
||||
}
|
||||
|
||||
// Render template
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
err = tmpl.Execute(w, data)
|
||||
if err != nil {
|
||||
log.Error(r.Context(), "Error executing aplayer template", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func checkShareError(ctx context.Context, w http.ResponseWriter, err error, id string) {
|
||||
switch {
|
||||
case errors.Is(err, model.ErrExpired):
|
||||
|
||||
@ -57,6 +57,7 @@ func (pub *Router) routes() http.Handler {
|
||||
r.HandleFunc("/d/{id}", pub.handleDownloads)
|
||||
}
|
||||
r.HandleFunc("/{id}/m3u", pub.handleM3U)
|
||||
r.HandleFunc("/{id}/aplayer", pub.handleAPlayer)
|
||||
r.HandleFunc("/{id}", pub.handleShares)
|
||||
r.HandleFunc("/", pub.handleShares)
|
||||
r.Handle("/*", pub.assetsHandler)
|
||||
|
||||
@ -6,20 +6,34 @@ import {
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
} from 'react-admin'
|
||||
import { sharePlayerUrl } from '../utils'
|
||||
import { Link } from '@material-ui/core'
|
||||
import { sharePlayerUrl, shareAPlayerUrl } from '../utils'
|
||||
import { Link, Box, Typography } from '@material-ui/core'
|
||||
import { DateField } from '../common'
|
||||
import config from '../config'
|
||||
|
||||
export const ShareEdit = (props) => {
|
||||
const { id, basePath, hasCreate, ...rest } = props
|
||||
const url = sharePlayerUrl(id)
|
||||
const aplayerUrl = shareAPlayerUrl(id)
|
||||
return (
|
||||
<Edit {...props}>
|
||||
<SimpleForm {...rest}>
|
||||
<Link source="URL" href={url} target="_blank" rel="noopener noreferrer">
|
||||
{url}
|
||||
</Link>
|
||||
<Box mb={2}>
|
||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||
Share URL
|
||||
</Typography>
|
||||
<Link source="URL" href={url} target="_blank" rel="noopener noreferrer">
|
||||
{url}
|
||||
</Link>
|
||||
</Box>
|
||||
<Box mb={2}>
|
||||
<Typography variant="body2" color="textSecondary" gutterBottom>
|
||||
APlayer Embed URL
|
||||
</Typography>
|
||||
<Link source="APlayerURL" href={aplayerUrl} target="_blank" rel="noopener noreferrer">
|
||||
{aplayerUrl}
|
||||
</Link>
|
||||
</Box>
|
||||
<TextInput source="description" />
|
||||
{config.enableDownloads && <BooleanInput source="downloadable" />}
|
||||
<DateTimeInput source="expiresAt" />
|
||||
|
||||
@ -25,6 +25,14 @@ export const sharePlayerUrl = (id) => {
|
||||
return url.href
|
||||
}
|
||||
|
||||
export const shareAPlayerUrl = (id) => {
|
||||
const url = new URL(
|
||||
shareUrl(config.publicBaseUrl + '/' + id + '/aplayer'),
|
||||
window.location.href,
|
||||
)
|
||||
return url.href
|
||||
}
|
||||
|
||||
export const shareStreamUrl = (id) => {
|
||||
return shareUrl(config.publicBaseUrl + '/s/' + id)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user