Merge branch 'main' into feature/new-playlist-btn
This commit is contained in:
@@ -58,9 +58,7 @@ func init() {
|
||||
return err
|
||||
})
|
||||
flag.Func("volume-adjust-pct", "adjusts volume up or down by the given percentage (positive or negative)", func(s string) error {
|
||||
if strings.HasSuffix(s, "%") {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
s = strings.TrimSuffix(s, "%")
|
||||
v, err := strconv.ParseFloat(s, 64)
|
||||
VolumePctCLIArg = v
|
||||
return err
|
||||
|
||||
+6
-4
@@ -112,9 +112,11 @@ type NowPlayingPageConfig struct {
|
||||
}
|
||||
|
||||
type PlaybackConfig struct {
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
UseWaveformSeekbar bool
|
||||
Autoplay bool
|
||||
RepeatMode string
|
||||
SkipOneStarWhenShuffling bool
|
||||
SkipKeywordWhenShuffling string
|
||||
UseWaveformSeekbar bool
|
||||
}
|
||||
|
||||
type LocalPlaybackConfig struct {
|
||||
@@ -317,7 +319,7 @@ func (c *Config) WriteConfigFile(filepath string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.WriteFile(filepath, b, 0644)
|
||||
os.WriteFile(filepath, b, 0o644)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,9 +39,7 @@ type ImageCache struct {
|
||||
cache map[string]CacheItem
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("item not found")
|
||||
)
|
||||
var ErrNotFound = errors.New("item not found")
|
||||
|
||||
func (i *ImageCache) Init(ctx context.Context, evictionInterval time.Duration) {
|
||||
i.cache = make(map[string]CacheItem)
|
||||
@@ -238,5 +236,4 @@ func (i *ImageCache) EvictExpired() {
|
||||
delete(i.cache, key)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ type ImageManager struct {
|
||||
maxOnDiskCacheSizeBytes int64
|
||||
filesWrittenSinceLastPrune bool
|
||||
|
||||
serverFetchSema chan interface{}
|
||||
serverFetchSema chan any
|
||||
}
|
||||
|
||||
// NewImageManager returns a new ImageManager.
|
||||
@@ -64,7 +64,7 @@ func NewImageManager(ctx context.Context, s *ServerManager, baseCacheDir string)
|
||||
DefaultTTL: 1 * time.Minute,
|
||||
},
|
||||
maxOnDiskCacheSizeBytes: defaultDiskCacheSizeBytes,
|
||||
serverFetchSema: make(chan interface{}, maxConcurrentServerFetches),
|
||||
serverFetchSema: make(chan any, maxConcurrentServerFetches),
|
||||
}
|
||||
s.OnLogout(func() {
|
||||
i.thumbnailCache.Clear()
|
||||
|
||||
@@ -130,7 +130,6 @@ func (c *Client) Quit() error {
|
||||
|
||||
func (c *Client) sendRequest(path string) (string, error) {
|
||||
resp, err := c.httpC.Get("http://supersonic/" + path)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (s *serverImpl) createHandler() http.Handler {
|
||||
search = strings.ToLower(search)
|
||||
|
||||
filtered := make([]mediaprovider.Playlist, 0)
|
||||
for i := 0; i < len(all); i++ {
|
||||
for i := range all {
|
||||
playlist := all[i]
|
||||
name := strings.ReplaceAll(playlist.Name, " ", "")
|
||||
name = strings.ToLower(name)
|
||||
|
||||
+18
-2
@@ -80,10 +80,26 @@ func (l *LrcLibFetcher) fetchFromServer(name, artist, album string, durationSecs
|
||||
req.Header.Add("Accept", "application/json")
|
||||
req.Header.Add("User-Agent", "Supersonic")
|
||||
|
||||
// Navidrome and Gonic substitute "[Unknown Album]" and "Unknown Album", respectively,
|
||||
// for an empty album name. This will break LrcLib matching.
|
||||
// TODO: if OpenSubsonic later clarifies that servers should not do this, remove this workaround.
|
||||
// N.B.: This workaround will break if servers decide to internationalize the default album name
|
||||
if strings.Contains(album, "Unknown Album") {
|
||||
album = ""
|
||||
}
|
||||
if strings.Contains(artist, "Unknown Artist") {
|
||||
artist = ""
|
||||
}
|
||||
|
||||
q := req.URL.Query()
|
||||
addIf := func(key, value string) {
|
||||
if value != "" {
|
||||
q.Add(key, value)
|
||||
}
|
||||
}
|
||||
q.Add("track_name", name)
|
||||
q.Add("artist_name", artist)
|
||||
q.Add("album_name", album)
|
||||
addIf("artist_name", artist)
|
||||
addIf("album_name", album)
|
||||
q.Add("duration", strconv.Itoa(durationSecs))
|
||||
req.URL.RawQuery = q.Encode()
|
||||
|
||||
|
||||
@@ -348,7 +348,7 @@ func (j *jellyfinMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteP
|
||||
}
|
||||
|
||||
numBatches := int(math.Ceil(float64(len(allIDs)) / float64(batchSize)))
|
||||
for i := 0; i < numBatches; i++ {
|
||||
for i := range numBatches {
|
||||
var wg sync.WaitGroup
|
||||
batchSetFavorite(i*batchSize, &wg)
|
||||
wg.Wait()
|
||||
@@ -434,6 +434,8 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track {
|
||||
coverArtID = ch.Id
|
||||
}
|
||||
|
||||
lastPlayed, _ := time.Parse(time.RFC3339Nano, ch.UserData.LastPlayedDate)
|
||||
|
||||
t := &mediaprovider.Track{
|
||||
ID: ch.Id,
|
||||
CoverArtID: coverArtID,
|
||||
@@ -442,7 +444,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track {
|
||||
Duration: time.Duration(ch.RunTimeTicks/runTimeTicksPerMicrosecond) * time.Microsecond,
|
||||
TrackNumber: ch.IndexNumber,
|
||||
DiscNumber: ch.DiscNumber,
|
||||
//Genre: ch.Genres,
|
||||
// Genre: ch.Genres,
|
||||
ArtistIDs: artistIDs,
|
||||
ArtistNames: artistNames,
|
||||
Album: ch.Album,
|
||||
@@ -451,6 +453,7 @@ func toTrack(ch *jellyfin.Song) *mediaprovider.Track {
|
||||
Rating: ch.UserData.Rating,
|
||||
Favorite: ch.UserData.IsFavorite,
|
||||
PlayCount: ch.UserData.PlayCount,
|
||||
LastPlayed: lastPlayed,
|
||||
}
|
||||
if len(ch.MediaSources) > 0 {
|
||||
t.FilePath = ch.MediaSources[0].Path
|
||||
|
||||
@@ -32,9 +32,11 @@ type MediaIterator[M any] interface {
|
||||
Next() *M
|
||||
}
|
||||
|
||||
type ArtistIterator = MediaIterator[Artist]
|
||||
type AlbumIterator = MediaIterator[Album]
|
||||
type TrackIterator = MediaIterator[Track]
|
||||
type (
|
||||
ArtistIterator = MediaIterator[Artist]
|
||||
AlbumIterator = MediaIterator[Album]
|
||||
TrackIterator = MediaIterator[Track]
|
||||
)
|
||||
|
||||
type MediaFilter[M, F any] interface {
|
||||
Options() F
|
||||
|
||||
@@ -22,11 +22,8 @@ func (s *subsonicMediaProvider) ArtistSortOrders() []string {
|
||||
}
|
||||
}
|
||||
|
||||
func filterArtistMatches(f mediaprovider.ArtistFilter, artist *subsonic.ArtistID3) bool {
|
||||
if artist == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
func filterArtistMatches(_ mediaprovider.ArtistFilter, artist *subsonic.ArtistID3) bool {
|
||||
return artist != nil
|
||||
}
|
||||
|
||||
func (s *subsonicMediaProvider) IterateArtists(sortOrder string, filter mediaprovider.ArtistFilter) mediaprovider.ArtistIterator {
|
||||
@@ -176,9 +173,7 @@ func (s *subsonicMediaProvider) artistFetchFnFromStandardSort(sortFn func([]*sub
|
||||
}
|
||||
var artists []*subsonic.ArtistID3
|
||||
for _, idx := range idxs.Index {
|
||||
for _, ar := range idx.Artist {
|
||||
artists = append(artists, ar)
|
||||
}
|
||||
artists = append(artists, idx.Artist...)
|
||||
}
|
||||
artists = sortFn(artists)
|
||||
return artists, nil
|
||||
|
||||
@@ -319,7 +319,8 @@ func (s *subsonicMediaProvider) ClientDecidesScrobble() bool { return true }
|
||||
func (s *subsonicMediaProvider) TrackBeganPlayback(trackID string) error {
|
||||
return s.client.Scrobble(trackID, map[string]string{
|
||||
"time": strconv.FormatInt(time.Now().UnixMilli(), 10),
|
||||
"submission": "false"})
|
||||
"submission": "false",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *subsonicMediaProvider) TrackEndedPlayback(trackID string, _ int, submission bool) error {
|
||||
@@ -328,7 +329,8 @@ func (s *subsonicMediaProvider) TrackEndedPlayback(trackID string, _ int, submis
|
||||
}
|
||||
return s.client.Scrobble(trackID, map[string]string{
|
||||
"time": strconv.FormatInt(time.Now().UnixMilli(), 10),
|
||||
"submission": "true"})
|
||||
"submission": "true",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *subsonicMediaProvider) SetFavorite(params mediaprovider.RatingFavoriteParameters, favorite bool) error {
|
||||
@@ -363,7 +365,7 @@ func (s *subsonicMediaProvider) SetRating(params mediaprovider.RatingFavoritePar
|
||||
}
|
||||
|
||||
numBatches := int(math.Ceil(float64(len(params.TrackIDs)) / float64(batchSize)))
|
||||
for i := 0; i < numBatches; i++ {
|
||||
for i := range numBatches {
|
||||
var wg sync.WaitGroup
|
||||
batchSetRating(i*batchSize, &wg)
|
||||
wg.Wait()
|
||||
|
||||
+1
-3
@@ -24,9 +24,7 @@ var (
|
||||
_ types.OrgMprisMediaPlayer2PlayerAdapterLoopStatus = (*MPRISHandler)(nil)
|
||||
)
|
||||
|
||||
var (
|
||||
errNotSupported = errors.New("not supported")
|
||||
)
|
||||
var errNotSupported = errors.New("not supported")
|
||||
|
||||
type MPRISHandler struct {
|
||||
// Function called if the player is requested to quit through MPRIS.
|
||||
|
||||
+33
-19
@@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/charlievieth/strcase"
|
||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||
"github.com/dweymouth/supersonic/backend/player"
|
||||
"github.com/dweymouth/supersonic/backend/player/dlna"
|
||||
@@ -27,7 +28,8 @@ type PlaybackManager struct {
|
||||
wfmGen *WaveformImageGenerator
|
||||
cache *AudioCache
|
||||
cmdQueue *playbackCommandQueue
|
||||
cfg *AppConfig
|
||||
appCfg *AppConfig
|
||||
cfg *PlaybackConfig
|
||||
|
||||
localPlayer player.BasePlayer
|
||||
remotePlayersLock sync.Mutex
|
||||
@@ -36,8 +38,6 @@ type PlaybackManager struct {
|
||||
|
||||
onWaveformImgUpdate []func(*WaveformImage)
|
||||
|
||||
autoplay bool
|
||||
|
||||
lastPlayTime float64
|
||||
lastPlayingID string
|
||||
wfmUpdateImageCancel context.CancelFunc
|
||||
@@ -66,8 +66,8 @@ func NewPlaybackManager(
|
||||
pm := &PlaybackManager{
|
||||
engine: e,
|
||||
cmdQueue: q,
|
||||
cfg: appCfg,
|
||||
autoplay: playbackCfg.Autoplay,
|
||||
appCfg: appCfg,
|
||||
cfg: playbackCfg,
|
||||
localPlayer: p,
|
||||
cache: c,
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (p *PlaybackManager) addOnTrackChangeHook() {
|
||||
|
||||
p.OnSongChange(func(item mediaprovider.MediaItem, _ *mediaprovider.Track) {
|
||||
// Autoplay if enabled and we are on the last track
|
||||
if p.autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
if p.cfg.Autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
p.enqueueAutoplayTracks()
|
||||
}
|
||||
p.handleWaveformImageSongChange(item)
|
||||
@@ -470,13 +470,23 @@ func (p *PlaybackManager) PlayTrackAt(idx int) {
|
||||
|
||||
func (p *PlaybackManager) PlayRandomSongs(genreName string) error {
|
||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) {
|
||||
return p.engine.sm.Server.GetRandomTracks(genreName, p.cfg.EnqueueBatchSize)
|
||||
tr, err := p.engine.sm.Server.GetRandomTracks(genreName, p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sharedutil.FilterSlice(tr, func(t *mediaprovider.Track) bool {
|
||||
skipKwd := p.cfg.SkipKeywordWhenShuffling
|
||||
include :=
|
||||
(skipKwd == "" || !strcase.Contains(t.Title, skipKwd)) &&
|
||||
(!p.cfg.SkipOneStarWhenShuffling || t.Rating != 1)
|
||||
return include
|
||||
}), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) PlaySimilarSongs(id string) error {
|
||||
return p.fetchAndPlayTracks(func() ([]*mediaprovider.Track, error) {
|
||||
return p.engine.sm.Server.GetSimilarTracks(id, p.cfg.EnqueueBatchSize)
|
||||
return p.engine.sm.Server.GetSimilarTracks(id, p.appCfg.EnqueueBatchSize)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -498,7 +508,7 @@ func (p *PlaybackManager) PlayRandomAlbums(genreName string) error {
|
||||
}
|
||||
iter := mp.IterateAlbums(mediaprovider.AlbumSortRandom, mediaprovider.NewAlbumFilter(options))
|
||||
insertMode := Replace
|
||||
for i := 0; i < 20; i++ {
|
||||
for i := range 20 {
|
||||
al := iter.Next()
|
||||
if al == nil {
|
||||
break
|
||||
@@ -592,7 +602,7 @@ func (p *PlaybackManager) GetLoopMode() LoopMode {
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) IsAutoplay() bool {
|
||||
return p.autoplay
|
||||
return p.cfg.Autoplay
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) PlaybackStatus() PlaybackStatus {
|
||||
@@ -604,7 +614,7 @@ func (p *PlaybackManager) SetVolume(vol int) {
|
||||
}
|
||||
|
||||
func (p *PlaybackManager) SetAutoplay(autoplay bool) {
|
||||
p.autoplay = autoplay
|
||||
p.cfg.Autoplay = autoplay
|
||||
if autoplay && p.NowPlayingIndex() == len(p.engine.playQueue)-1 {
|
||||
p.enqueueAutoplayTracks()
|
||||
}
|
||||
@@ -705,11 +715,15 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
// tracks we will enqueue
|
||||
var tracks []*mediaprovider.Track
|
||||
|
||||
filterRecentlyPlayed := func(tracks []*mediaprovider.Track) []*mediaprovider.Track {
|
||||
filterAutoplayTracks := func(tracks []*mediaprovider.Track) []*mediaprovider.Track {
|
||||
return sharedutil.FilterSlice(tracks, func(t *mediaprovider.Track) bool {
|
||||
return !slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool {
|
||||
shouldSkip :=
|
||||
(p.cfg.SkipOneStarWhenShuffling && t.Rating == 1) ||
|
||||
(p.cfg.SkipKeywordWhenShuffling != "" && strcase.Contains(t.Title, p.cfg.SkipKeywordWhenShuffling))
|
||||
recentlyPlayed := slices.ContainsFunc(queue, func(i mediaprovider.MediaItem) bool {
|
||||
return i.Metadata().Type == mediaprovider.MediaItemTypeTrack && i.Metadata().ID == t.ID
|
||||
})
|
||||
return !shouldSkip && !recentlyPlayed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -722,11 +736,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
|
||||
// similar tracks by artist
|
||||
if len(tr.ArtistIDs) > 0 {
|
||||
similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.cfg.EnqueueBatchSize)
|
||||
similar, err := s.GetSimilarTracks(tr.ArtistIDs[0], p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get similar tracks: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(similar)
|
||||
tracks = filterAutoplayTracks(similar)
|
||||
}
|
||||
|
||||
// fallback to random tracks from genre
|
||||
@@ -735,11 +749,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
if g == "" {
|
||||
continue
|
||||
}
|
||||
byGenre, err := s.GetRandomTracks(g, p.cfg.EnqueueBatchSize)
|
||||
byGenre, err := s.GetRandomTracks(g, p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get tracks by genre: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(byGenre)
|
||||
tracks = filterAutoplayTracks(byGenre)
|
||||
if len(tracks) > 0 {
|
||||
break
|
||||
}
|
||||
@@ -750,11 +764,11 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
// random tracks works regardless of the type of the last playing media
|
||||
if len(tracks) == 0 {
|
||||
// fallback to random tracks
|
||||
random, err := s.GetRandomTracks("", p.cfg.EnqueueBatchSize)
|
||||
random, err := s.GetRandomTracks("", p.appCfg.EnqueueBatchSize)
|
||||
if err != nil {
|
||||
log.Printf("autoplay error: failed to get random tracks: %v", err)
|
||||
}
|
||||
tracks = filterRecentlyPlayed(random)
|
||||
tracks = filterAutoplayTracks(random)
|
||||
}
|
||||
|
||||
if len(tracks) > 0 {
|
||||
|
||||
@@ -618,7 +618,7 @@ func (d *DLNAPlayer) lookupProxyURL(key string) (string, bool) {
|
||||
d.proxyURLLock.Lock()
|
||||
defer d.proxyURLLock.Unlock()
|
||||
|
||||
for i := 0; i < len(d.proxyURLs); i++ {
|
||||
for i := range len(d.proxyURLs) {
|
||||
if d.proxyURLs[i].key == key {
|
||||
url := d.proxyURLs[i].url
|
||||
// Move accessed entry to the most recent position
|
||||
@@ -632,7 +632,7 @@ func (d *DLNAPlayer) lookupProxyURL(key string) (string, bool) {
|
||||
|
||||
func (d *DLNAPlayer) _updateProxyURL(key, url string) {
|
||||
// Check if the key already exists, and if so, move it to the most recently used position
|
||||
for i := 0; i < len(d.proxyURLs); i++ {
|
||||
for i := range len(d.proxyURLs) {
|
||||
if d.proxyURLs[i].key == key {
|
||||
if i < len(d.proxyURLs)-1 {
|
||||
// Shift elements to the left from found position to the end
|
||||
@@ -652,19 +652,19 @@ func (d *DLNAPlayer) _updateProxyURL(key, url string) {
|
||||
|
||||
type retryLogger struct{}
|
||||
|
||||
func (retryLogger) Error(msg string, keysAndValues ...interface{}) {
|
||||
func (retryLogger) Error(msg string, keysAndValues ...any) {
|
||||
log.Println(msg, keysAndValues)
|
||||
}
|
||||
|
||||
func (retryLogger) Info(msg string, keysAndValues ...interface{}) {
|
||||
func (retryLogger) Info(msg string, keysAndValues ...any) {
|
||||
log.Println(msg, keysAndValues)
|
||||
}
|
||||
|
||||
func (retryLogger) Warn(msg string, keysAndValues ...interface{}) {
|
||||
func (retryLogger) Warn(msg string, keysAndValues ...any) {
|
||||
log.Println(msg, keysAndValues)
|
||||
}
|
||||
|
||||
func (retryLogger) Debug(msg string, keysAndValues ...interface{}) {
|
||||
func (retryLogger) Debug(msg string, keysAndValues ...any) {
|
||||
// log only retries, not every request
|
||||
if strings.Contains(msg, "retrying request") {
|
||||
log.Println(msg, keysAndValues)
|
||||
|
||||
@@ -109,7 +109,6 @@ func (j *JukeboxPlayer) SetNextTrack(track *mediaprovider.Track) error {
|
||||
}
|
||||
j.queueLength += 1
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (j *JukeboxPlayer) SeekSeconds(secs float64) error {
|
||||
|
||||
@@ -3,6 +3,7 @@ package mpv
|
||||
// #include <mpv/client.h>
|
||||
// int mpv_get_peaks(mpv_handle* handle, double* lPeak, double* rPeak, double* lRMS, double* rRMS);
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"github.com/supersonic-app/go-mpv"
|
||||
)
|
||||
|
||||
@@ -492,7 +492,7 @@ func (p *Player) eventHandler(ctx context.Context) {
|
||||
default:
|
||||
e := p.mpv.WaitEvent(1 /*timeout seconds*/)
|
||||
if e.Event_Id != mpv.EVENT_NONE {
|
||||
//log.Printf("mpv event: %+v\n", e)
|
||||
// log.Printf("mpv event: %+v\n", e)
|
||||
}
|
||||
switch e.Event_Id {
|
||||
case mpv.EVENT_PLAYBACK_RESTART:
|
||||
|
||||
@@ -51,7 +51,7 @@ func SavePlayQueue(serverID string, pm *PlaybackManager, filepath string, server
|
||||
TimePos: stats.TimePos,
|
||||
}
|
||||
b, _ := json.Marshal(saved)
|
||||
err := os.WriteFile(filepath, b, 0644)
|
||||
err := os.WriteFile(filepath, b, 0o644)
|
||||
|
||||
if server != nil {
|
||||
// save to server
|
||||
|
||||
@@ -86,7 +86,7 @@ func (w *WaveformImageJob) Get() *WaveformImage {
|
||||
result := NewWaveformImage()
|
||||
|
||||
// Copy each scanline from w.img to result
|
||||
for y := 0; y < height; y++ {
|
||||
for y := range height {
|
||||
srcOffset := w.img.PixOffset(0, y)
|
||||
dstOffset := result.PixOffset(0, y)
|
||||
copy(result.Pix[dstOffset:dstOffset+w.progress*4], w.img.Pix[srcOffset:srcOffset+w.progress*4])
|
||||
@@ -226,7 +226,7 @@ func generateWaveformImage(ctx context.Context, data *waveformData, job *Wavefor
|
||||
opaqueColor := color.NRGBA{R: 255, G: 255, B: 255, A: 255}
|
||||
translucentColor := color.NRGBA{R: 255, G: 255, B: 255, A: 128}
|
||||
|
||||
for x := 0; x < 1024; x++ {
|
||||
for x := range 1024 {
|
||||
for data.progress <= x {
|
||||
if data.done {
|
||||
return
|
||||
@@ -308,7 +308,7 @@ func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformDat
|
||||
currentSize := stat.Size()
|
||||
|
||||
// how many bytes can we read without nearing EOF
|
||||
readableBytes := currentSize - int64(samplesPerChunk)*int64(curChunk)*bytesPerSample - 8192 //buffer for safety
|
||||
readableBytes := currentSize - int64(samplesPerChunk)*int64(curChunk)*bytesPerSample - 8192 // buffer for safety
|
||||
|
||||
// Estimate how many samples we can read
|
||||
maxSamples := int(readableBytes / bytesPerSample)
|
||||
@@ -341,7 +341,7 @@ func analyzeWavFile(ctx context.Context, transcodeFile string, data *waveformDat
|
||||
}
|
||||
|
||||
// Process samples
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
sample := float64(buf.Data[i]) / float64(1<<15) // Normalize to [-1, 1]
|
||||
chunkSamples = append(chunkSamples, sample)
|
||||
|
||||
@@ -391,7 +391,7 @@ func computePeakAndRMS(chunk []float64) (peak float64, rms float64) {
|
||||
sumSquares += float64(v * v)
|
||||
}
|
||||
rms = math.Sqrt(sumSquares / float64(len(chunk)))
|
||||
return
|
||||
return peak, rms
|
||||
}
|
||||
|
||||
func float64ToByte(val float64) byte {
|
||||
|
||||
@@ -17,8 +17,10 @@ import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
type SMTCPlaybackState int
|
||||
type SMTCButton int
|
||||
type (
|
||||
SMTCPlaybackState int
|
||||
SMTCButton int
|
||||
)
|
||||
|
||||
const (
|
||||
// constants from smtc.h in github.com/supersonic-app/smtc-dll
|
||||
|
||||
@@ -4,8 +4,10 @@ package windows
|
||||
|
||||
import "errors"
|
||||
|
||||
type SMTCPlaybackState int
|
||||
type SMTCButton int
|
||||
type (
|
||||
SMTCPlaybackState int
|
||||
SMTCButton int
|
||||
)
|
||||
|
||||
const (
|
||||
// constants from smtc.h in github.com/supersonic-app/smtc-dll
|
||||
@@ -22,18 +24,18 @@ const (
|
||||
|
||||
type SMTC struct{}
|
||||
|
||||
var smtcUnsupportedErr = errors.New("SMTC is not supported on this platformo")
|
||||
var errSMTCUnsupported = errors.New("no support for SMTC on this platform")
|
||||
|
||||
func InitSMTCForWindow(hwnd uintptr) (*SMTC, error) {
|
||||
return nil, smtcUnsupportedErr
|
||||
return nil, errSMTCUnsupported
|
||||
}
|
||||
|
||||
func (s *SMTC) SetEnabled(enabled bool) error {
|
||||
return smtcUnsupportedErr
|
||||
return errSMTCUnsupported
|
||||
}
|
||||
|
||||
func (s *SMTC) SetThumbnail(filepath string) error {
|
||||
return smtcUnsupportedErr
|
||||
return errSMTCUnsupported
|
||||
}
|
||||
|
||||
func (s *SMTC) OnButtonPressed(func(SMTCButton)) {}
|
||||
@@ -43,13 +45,13 @@ func (s *SMTC) OnSeek(f func(millis int)) {}
|
||||
func (s *SMTC) Shutdown() {}
|
||||
|
||||
func (s *SMTC) UpdatePlaybackState(state SMTCPlaybackState) error {
|
||||
return smtcUnsupportedErr
|
||||
return errSMTCUnsupported
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdateMetadata(title, artist string) error {
|
||||
return smtcUnsupportedErr
|
||||
return errSMTCUnsupported
|
||||
}
|
||||
|
||||
func (s *SMTC) UpdatePosition(positionMillis, durationMillis int) error {
|
||||
return smtcUnsupportedErr
|
||||
return errSMTCUnsupported
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package windows
|
||||
extern void goButtonClicked(int);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
|
||||
Reference in New Issue
Block a user