diff --git a/.github/workflows/build-appimage-compat.yml b/.github/workflows/build-appimage-compat.yml index 0a476e30..3c8577c7 100644 --- a/.github/workflows/build-appimage-compat.yml +++ b/.github/workflows/build-appimage-compat.yml @@ -32,7 +32,7 @@ jobs: ./appimage-build-compat.sh - name: upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic-compat.AppImage path: Supersonic-x86_64.AppImage diff --git a/.github/workflows/build-appimage.yml b/.github/workflows/build-appimage.yml index 8b44c7e6..1bc96c2d 100644 --- a/.github/workflows/build-appimage.yml +++ b/.github/workflows/build-appimage.yml @@ -32,7 +32,7 @@ jobs: ./appimage-build.sh - name: upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic.AppImage path: Supersonic-x86_64.AppImage diff --git a/.github/workflows/build-macos-arm64.yml b/.github/workflows/build-macos-arm64.yml index d18397df..8a017a77 100644 --- a/.github/workflows/build-macos-arm64.yml +++ b/.github/workflows/build-macos-arm64.yml @@ -44,7 +44,7 @@ jobs: make zip_macos - name: Upload package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic.zip path: Supersonic.zip diff --git a/.github/workflows/build-macos-x64.yml b/.github/workflows/build-macos-x64.yml index bb79ac78..5d97b6d8 100644 --- a/.github/workflows/build-macos-x64.yml +++ b/.github/workflows/build-macos-x64.yml @@ -49,13 +49,13 @@ jobs: mv Supersonic.zip Supersonic_HighSierra+.zip - name: Upload Big Sur+ package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_mac_x64_BigSur+.zip path: Supersonic_BigSur+.zip - name: Upload High Sierra+ package - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_mac_x64_HighSierra+.zip path: Supersonic_HighSierra+.zip diff --git a/.github/workflows/build-ubuntu-22.04.yml b/.github/workflows/build-ubuntu-22.04.yml index 1b0c4c69..2e4d48b6 100644 --- a/.github/workflows/build-ubuntu-22.04.yml +++ b/.github/workflows/build-ubuntu-22.04.yml @@ -33,7 +33,7 @@ jobs: run: make package_linux - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_ubuntu_x64.tar.xz path: Supersonic.tar.xz diff --git a/.github/workflows/build-ubuntu-24.04.yml b/.github/workflows/build-ubuntu-24.04.yml index c19b6278..b2402e32 100644 --- a/.github/workflows/build-ubuntu-24.04.yml +++ b/.github/workflows/build-ubuntu-24.04.yml @@ -33,7 +33,7 @@ jobs: run: make package_linux - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_ubuntu_x64.tar.xz path: Supersonic.tar.xz diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index a2b61539..bf24cbb1 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -55,7 +55,7 @@ jobs: - name: Download smtc dll run: > - wget https://github.com/supersonic-app/smtc-dll/releases/download/v0.0.1/SMTC.dll + wget https://github.com/supersonic-app/smtc-dll/releases/download/v0.1.0/SMTC.dll - name: Generate zip bundle run: zip Supersonic-windows.zip Supersonic.exe libmpv-2.dll SMTC.dll @@ -66,13 +66,13 @@ jobs: path: win_inno_installscript.iss - name: Upload zip - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_windows_x64.zip path: Supersonic-windows.zip - name: Upload installer - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: Supersonic_windows_x64_installer.exe path: Output/supersonic-installer.exe diff --git a/backend/app.go b/backend/app.go index 9a014ab6..620ed2fd 100644 --- a/backend/app.go +++ b/backend/app.go @@ -53,11 +53,12 @@ type App struct { OnReactivate func() OnExit func() - appName string - appVersionTag string - configDir string - cacheDir string - portableMode bool + appName string + displayAppName string + appVersionTag string + configDir string + cacheDir string + portableMode bool isFirstLaunch bool // set by config file reader bgrndCtx context.Context @@ -97,12 +98,13 @@ func StartupApp(appName, displayAppName, appVersion, appVersionTag, latestReleas } a := &App{ - logFile: logFile, - appName: appName, - appVersionTag: appVersionTag, - configDir: confDir, - cacheDir: cacheDir, - portableMode: portableMode, + logFile: logFile, + appName: appName, + displayAppName: displayAppName, + appVersionTag: appVersionTag, + configDir: confDir, + cacheDir: cacheDir, + portableMode: portableMode, } a.bgrndCtx, a.cancel = context.WithCancel(context.Background()) a.readConfig() @@ -343,6 +345,7 @@ func (a *App) SetupWindowsSMTC(hwnd uintptr) { return } a.WinSMTC = smtc + smtc.UpdateMetadata(a.displayAppName, "") smtc.OnButtonPressed(func(btn SMTCButton) { switch btn { @@ -364,20 +367,35 @@ func (a *App) SetupWindowsSMTC(hwnd uintptr) { a.PlaybackManager.OnSongChange(func(nowPlaying mediaprovider.MediaItem, _ *mediaprovider.Track) { if nowPlaying == nil { - smtc.UpdateMetadata("", "") + smtc.UpdateMetadata("Supersonic", "") return } - artist := strings.Join(nowPlaying.Metadata().Artists, ", ") - smtc.UpdateMetadata(nowPlaying.Metadata().Name, artist) - smtc.UpdatePosition(0, nowPlaying.Metadata().Duration*1000) + meta := nowPlaying.Metadata() + smtc.UpdateMetadata(meta.Name, strings.Join(meta.Artists, ", ")) + smtc.UpdatePosition(0, meta.Duration*1000) + go func() { + a.ImageManager.GetCoverThumbnail(meta.CoverArtID) // ensure image is cached locally + if path, err := a.ImageManager.GetCoverArtPath(meta.CoverArtID); err == nil { + smtc.SetThumbnail(path) + } + }() }) a.PlaybackManager.OnSeek(func() { dur := a.PlaybackManager.NowPlaying().Metadata().Duration smtc.UpdatePosition(int(a.PlaybackManager.CurrentPlayer().GetStatus().TimePos*1000), dur*1000) }) - a.PlaybackManager.OnPlaying(func() { smtc.UpdatePlaybackState(SMTCPlaybackStatePlaying) }) - a.PlaybackManager.OnPaused(func() { smtc.UpdatePlaybackState(SMTCPlaybackStatePaused) }) - a.PlaybackManager.OnStopped(func() { smtc.UpdatePlaybackState(SMTCPlaybackStateStopped) }) + a.PlaybackManager.OnPlaying(func() { + smtc.SetEnabled(true) + smtc.UpdatePlaybackState(SMTCPlaybackStatePlaying) + }) + a.PlaybackManager.OnPaused(func() { + smtc.SetEnabled(true) + smtc.UpdatePlaybackState(SMTCPlaybackStatePaused) + }) + a.PlaybackManager.OnStopped(func() { + smtc.SetEnabled(false) + smtc.UpdatePlaybackState(SMTCPlaybackStateStopped) + }) } func (a *App) LoginToDefaultServer(string) error { diff --git a/backend/imagemanager.go b/backend/imagemanager.go index b39c7c0d..e824d42b 100644 --- a/backend/imagemanager.go +++ b/backend/imagemanager.go @@ -160,6 +160,15 @@ func (i *ImageManager) GetCoverArtUrl(coverID string) (string, error) { return "", errors.New("cover not found") } +// GetCoverArtURL returns the file path for the locally cached cover thumbnail, if it exists. +func (i *ImageManager) GetCoverArtPath(coverID string) (string, error) { + path := i.filePathForCover(coverID) + if _, err := os.Stat(path); err == nil { + return path, nil + } + return "", errors.New("cover not found") +} + // GetCachedArtistImage returns the artist image for the given artistID from the on-disc cache, if it exists. func (i *ImageManager) GetCachedArtistImage(artistID string) (image.Image, bool) { return i.loadLocalImage(i.filePathForArtistImage(artistID)) diff --git a/backend/smtc.go b/backend/smtc.go index 095b0841..0c003739 100644 --- a/backend/smtc.go +++ b/backend/smtc.go @@ -93,13 +93,13 @@ func (s *SMTC) UpdatePlaybackState(state SMTCPlaybackState) error { return errors.New("SMTC DLL not available") } - proc, err := s.dll.FindProc("UpdatePlaybackState") + proc, err := s.dll.FindProc("SetPlaybackState") if err != nil { return err } if hr, _, _ := proc.Call(uintptr(state)); hr < 0 { - return fmt.Errorf("UpdatePlaybackState failed with HRESULT=%d", hr) + return fmt.Errorf("SetPlaybackState failed with HRESULT=%d", hr) } return nil } @@ -119,14 +119,14 @@ func (s *SMTC) UpdateMetadata(title, artist string) error { return err } - proc, err := s.dll.FindProc("UpdateMetadata") + proc, err := s.dll.FindProc("SetMetadata") if err != nil { return err } hr, _, _ := proc.Call(uintptr(unsafe.Pointer(utfTitle)), uintptr(unsafe.Pointer(utfArtist))) if hr < 0 { - return fmt.Errorf("UpdateMetadata failed with HRESULT=%d", hr) + return fmt.Errorf("SetMetadata failed with HRESULT=%d", hr) } return nil } @@ -136,14 +136,58 @@ func (s *SMTC) UpdatePosition(positionMillis, durationMillis int) error { return errors.New("SMTC DLL not available") } - proc, err := s.dll.FindProc("UpdatePosition") + proc, err := s.dll.FindProc("SetPosition") if err != nil { return err } hr, _, _ := proc.Call(uintptr(positionMillis), uintptr(durationMillis)) if hr < 0 { - return fmt.Errorf("UpdatePosition failed with HRESULT=%d", hr) + return fmt.Errorf("SetPosition failed with HRESULT=%d", hr) + } + return nil +} + +func (s *SMTC) SetThumbnail(filepath string) error { + if s.dll == nil { + return errors.New("SMTC DLL not available") + } + + proc, err := s.dll.FindProc("SetThumbnailPath") + if err != nil { + return err + } + + utfPath, err := windows.UTF16PtrFromString(filepath) + if err != nil { + return err + } + + hr, _, _ := proc.Call(uintptr(unsafe.Pointer(utfPath))) + if hr < 0 { + return fmt.Errorf("SetThumbnailPath failed with HRESULT=%d", hr) + } + return nil +} + +func (s *SMTC) SetEnabled(enabled bool) error { + if s.dll == nil { + return errors.New("SMTC DLL not available") + } + + proc, err := s.dll.FindProc("SetEnabled") + if err != nil { + return err + } + + var arg uintptr = 0 + if enabled { + arg = 1 + } + + hr, _, _ := proc.Call(arg) + if hr < 0 { + return fmt.Errorf("SetEnabled failed with HRESULT=%d", hr) } return nil }