-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bd160cf
Showing
42 changed files
with
5,599 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Configuration | ||
config*.json | ||
|
||
# Cache | ||
cache/ | ||
*.blob | ||
|
||
# Output executables | ||
*.exe | ||
*.app | ||
*.old | ||
*.bak | ||
|
||
# Most probable output executables for Linux | ||
libremedia.app | ||
|
||
# Log files | ||
*.log | ||
|
||
# Supervise | ||
run | ||
supervise/ | ||
|
||
# OS leftovers | ||
desktop.ini |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
### libremedia progress tracker before release | ||
|
||
# User interface | ||
|
||
- Fetch artwork for an object using `:artwork` URI extension instead of re-fetching the entire source object, use `:1280` to smartly limit size to 1280px or next size up for max quality | ||
- Add visibility toggle buttons to both the search bar and the audio player | ||
- Increase size of controls in tables to maximize room for touch screens, OR migrate all controls to drop-down list, whichever works out better (or condense controls to it if table entry is squished?) | ||
|
||
## Pages | ||
|
||
- Display top 100 streams and top 100 downloads on home page | ||
- Add "download album" control on album page, generates and saves single-folder ZIP of all streams client-side with per-stream progress bar | ||
- Add "download discography" control on creator page, generates and saves multi-folder ZIP of all albums, EPs and singles client-side with per-album and per-stream progress bars | ||
- Add playlists section on search and creator pages, uses same handler for album objects | ||
- Display entry numbers and total X of each table section | ||
|
||
- Create queue management using /queue with no params | ||
* Generic streams table, only shows either the regular queue or the shuffled queue | ||
* Add reorder control, injects clickable dividers in-between streams that will move selected stream to that location | ||
* Add unqueue control, removes stream from queue | ||
* Allow saving current queue as a libremedia playlist | ||
|
||
- Rewrite logic for transcript page | ||
* Fetch transcript with timings using `:transcript` URI extension instead of re-fetching entire stream object | ||
* Start audio position 0 with empty line (unless first line begins at pos 0) using timings index 0 instead of fake -1 like before (invalid index) | ||
* Signal unplayed/restarted auto-scroller position tracker using -1 instead of -2 like before | ||
* Always load now playing transcript into timings buffer when stream changes, automaticlly scroll back to top | ||
* Add fixed resume auto-scroll button to bottom right when user scrolls to disable auto-scroll | ||
|
||
## Audio player | ||
|
||
- Add vertical volume slider | ||
- Add horizontal audio position seeker directly above audio player, show regardless of audio player visibility | ||
- Change transcript button to specify no params instead of pointing to now playing stream URI, no params will sync transcript with now playing | ||
- Display smaller album art somewhere, use either same size or next size up for max quality | ||
|
||
- Add shuffled queue support | ||
* Place shuffle button somewhere | ||
* Generate new shuffled queue using existing queue on every enable, and use it for next/prev handling until disable | ||
* On disable, return to original queue regardless of what was next prior to enable | ||
|
||
# Backend | ||
|
||
- Start anonymously tracking listen and download counts | ||
- At end of object expansion goroutine, spawn new goroutine to search other providers for matching object to fill in missing metadata (such as credited creators, biographies, artwork, albums, streams, etc) | ||
- Migrate all client-side player controls to the server, simulating client actions based on client requests | ||
- Require clients to request to start playback (automatically acting as "I'm ready" for shared sessions to minimize latency), so they always load from `/v1/stream` with no params afterward | ||
- Add `/v1/providers` endpoint to return all upstream providers and a path to retrieve their icons, which can follow upstream to the source provider | ||
- Add providers array param to `/v1/search` endpoint, allows client-side filtering of providers via request (useful to minimize processing and deduplicate responses when a provider is shared across upstream instances) | ||
- Convert transcript handler to be separated transcript providers, also available as plugins | ||
- Allow catalogue and database providers to be implemented as multimedia providers, without the streams | ||
- Implement support for ffmpeg (for custom format, codec, and quality params, plus metadata injection with `/v1/download` endpoint) | ||
- Implement support for ffprobe (pointing to internal `/v1/stream` API) to identify format details if not available on provider, but direct stream is available | ||
- Find a reliable and free way to identify audio streams with no metadata | ||
|
||
## Plugins | ||
|
||
- Finish writing new plugin system, simple concept but a lot to explain, later | ||
- Implement the Rust librespot client as a binary plugin to provide Spotify, and add podcast support | ||
- Remove hardcoded Spotify provider | ||
- Migrate hardcoded Tidal provider to a binary plugin, and add music video support | ||
- Create plugins for many other things, like local content, disc drive / ISO support, torrent support, YouTube, Bandcamp, SoundCloud, Apple Music, Deezer, etc | ||
|
||
## User accounts | ||
|
||
- Provide a default guest user using server config | ||
- Provide a default admin user with permission to manage additional users and control the quality settings, globally and of each user | ||
|
||
- Cache now playing stream to disk as it buffers (can be random access too) | ||
* Hold lock on file until all sessions holding lock either timeout or all choose new streams | ||
* Avoid repeated reads with session sharing by using the same buffer, as all sessions are synced | ||
|
||
- Add session sharing support | ||
* Generate an invite link to share with any user | ||
* Sync queue, now playing, and audio position in real time (with both a low-latency and data saver mode client-side) | ||
* If sharer toggles it, users may vote to skip with >50% majority | ||
* If sharer toggles it, users may append to queue (with a rotational selection mode in client join order, and a free for all mode) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// GetObject returns an object, either from the cache, or live if possible | ||
func GetObject(uri string) (obj *Object) { | ||
//Try the cache first, it will update frequently during a live expansion | ||
obj = GetObjectCached(uri) | ||
if obj != nil { | ||
return obj | ||
} | ||
|
||
//Fetch the object live | ||
obj = GetObjectLive(uri) | ||
if obj == nil { | ||
return | ||
} | ||
|
||
obj.Sync() | ||
return | ||
} | ||
|
||
// GetObjectLive returns a live object from a given URI | ||
func GetObjectLive(mediaURI string) (obj *Object) { | ||
if mediaURI == "" { | ||
Error.Println("Cannot get object with empty mediaURI") | ||
return nil | ||
} | ||
|
||
obj = &Object{URI: mediaURI, Object: &json.RawMessage{}} | ||
Trace.Println("Fetching " + mediaURI + " live") | ||
|
||
splitURI := strings.Split(mediaURI, ":") | ||
switch splitURI[0] { | ||
case "bestmatch": //Returns an object that best matches the given search query | ||
if len(splitURI) < 2 { | ||
return NewObjError("bestmatch: need query") | ||
} | ||
query := strings.ReplaceAll(splitURI[1], "+", " ") | ||
query = strings.ReplaceAll(query, "%20", " ") | ||
query = strings.ToLower(query) | ||
searchResultsObj := GetObjectLive("search:" + splitURI[1]) | ||
if searchResultsObj.Type != "search" { | ||
return searchResultsObj | ||
} | ||
searchResults := searchResultsObj.SearchResults() | ||
if len(searchResults.Streams) > 0 { | ||
return GetObjectLive(searchResults.Streams[0].Stream().URI) | ||
} | ||
if len(searchResults.Creators) > 0 { | ||
return GetObjectLive(searchResults.Creators[0].Creator().URI) | ||
} | ||
if len(searchResults.Albums) > 0 { | ||
return GetObjectLive(searchResults.Albums[0].Album().URI) | ||
} | ||
return NewObjError("bestmatch: try a better query") | ||
case "search": //Main search handler | ||
if len(splitURI) < 2 { | ||
return NewObjError("search: need query") | ||
} | ||
query := strings.ReplaceAll(splitURI[1], "+", " ") | ||
query = strings.ReplaceAll(query, "%20", " ") | ||
query = strings.ToLower(query) | ||
obj.URI = "search:" + query | ||
results := &ObjectSearchResults{Query: query} | ||
for i := 0; i < len(providers); i++ { | ||
Trace.Println("Searching for '" + query + "' on " + providers[i]) | ||
handler := handlers[providers[i]] | ||
res, err := handler.Search(query) | ||
if err != nil { | ||
Error.Printf("Error searching on %s: %v", providers[i], err) | ||
continue | ||
} | ||
if len(res.Creators) > 0 { | ||
results.Creators = append(results.Creators, res.Creators...) | ||
} | ||
if len(res.Albums) > 0 { | ||
results.Albums = append(results.Albums, res.Albums...) | ||
} | ||
if len(res.Streams) > 0 { | ||
results.Streams = append(results.Streams, res.Streams...) | ||
} | ||
} | ||
obj.Type = "search" | ||
obj.Provider = "libremedia" | ||
resultsJSON, err := json.Marshal(results) | ||
if err != nil { | ||
Error.Printf("Unable to marshal search results: %v\n", err) | ||
return | ||
} | ||
|
||
obj.Object.UnmarshalJSON(resultsJSON) | ||
return | ||
} | ||
|
||
if handler, exists := handlers[splitURI[0]]; exists { | ||
obj.Provider = splitURI[0] | ||
if len(splitURI) > 2 { | ||
id := splitURI[2] | ||
switch splitURI[1] { | ||
case "artist", "creator", "user", "channel", "chan", "streamer": | ||
creator, err := handler.Creator(id) | ||
if err != nil { | ||
return NewObjError(fmt.Sprintf("invalid creator %s: %v", id, err)) | ||
} | ||
obj.Type = "creator" | ||
creatorJSON, err := json.Marshal(creator) | ||
if err != nil { | ||
Error.Printf("Unable to marshal creator: %v\n", err) | ||
return | ||
} | ||
obj.Object.UnmarshalJSON(creatorJSON) | ||
case "album": | ||
album, err := handler.Album(id) | ||
if err != nil { | ||
return NewObjError(fmt.Sprintf("invalid album %s: %v", id, err)) | ||
} | ||
obj.Type = "album" | ||
albumJSON, err := json.Marshal(album) | ||
if err != nil { | ||
Error.Printf("Unable to marshal album: %v\n", err) | ||
return | ||
} | ||
obj.Object.UnmarshalJSON(albumJSON) | ||
case "track", "song", "video", "audio", "stream": | ||
stream, err := handler.Stream(id) | ||
if err != nil { | ||
return NewObjError(fmt.Sprintf("invalid stream %s: %v", id, err)) | ||
} | ||
obj.Type = "stream" | ||
stream.Transcribe() | ||
streamJSON, err := json.Marshal(stream) | ||
if err != nil { | ||
Error.Printf("Unable to marshal stream: %v\n", err) | ||
return | ||
} | ||
obj.Object.UnmarshalJSON(streamJSON) | ||
} | ||
|
||
return obj | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#audioControls button { | ||
margin: 15px; | ||
font-size: 2em; | ||
box-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2); | ||
cursor: crosshair; | ||
text-align: center; | ||
|
||
padding: 0; | ||
border: none; | ||
background: none; | ||
color: white; | ||
background-color: transparent; | ||
-webkit-user-select: none; | ||
-moz-user-select: none; | ||
user-select: none; | ||
-webkit-appearance: none; | ||
-moz-appearance: none; | ||
appearance: none; | ||
} | ||
|
||
#audioControls button:focus { | ||
outline: none; | ||
} |
Oops, something went wrong.