Update: Add Media Session API for native mobile controls
This commit is contained in:
parent
e23714bbd6
commit
c8c232afe9
4 changed files with 81 additions and 5 deletions
13
README.md
13
README.md
|
|
@ -108,8 +108,17 @@ This app runs perfectly on Synology NAS using **Container Manager** (formerly Do
|
|||
- ./data:/app/backend/data
|
||||
```
|
||||
3. In Container Manager, go to **Project** -> **Create**.
|
||||
4. Select the folder path, give it a name, and it will detect the compose file.
|
||||
5. Click **Build** / **Run**.
|
||||
4. Select the folder path, give it a name, and it will detect the compose file.
|
||||
5. Click **Build** / **Run**.
|
||||
|
||||
#### ✨ Auto-Update Enabled
|
||||
When using the `docker-compose.yml` above, a **Watchtower** container is included. It will automatically:
|
||||
- Check for updates to `vndangkhoa/spotify-clone:latest` every hour.
|
||||
- Download the new image if available.
|
||||
- Restart the application with the new version.
|
||||
- Remove old image versions to save space.
|
||||
|
||||
You don't need to do anything manually to keep it updated! 🚀
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ fi
|
|||
|
||||
echo "Staging and Committing changes..."
|
||||
git add .
|
||||
git commit -m "Update: Fix empty album and iOS playback issues, add diverse home content"
|
||||
git commit -m "Update: Add Media Session API for native mobile controls"
|
||||
|
||||
echo "Pushing code..."
|
||||
# This might fail if the repo doesn't exist on GitHub yet.
|
||||
|
|
|
|||
|
|
@ -9,3 +9,13 @@ services:
|
|||
|
||||
volumes:
|
||||
- ./data:/app/backend/data
|
||||
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
container_name: spotify-watchtower
|
||||
restart: always
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
command: --interval 3600 --cleanup
|
||||
environment:
|
||||
- WATCHTOWER_INCLUDE_RESTARTING=true
|
||||
|
|
|
|||
|
|
@ -38,10 +38,53 @@ export default function PlayerBar() {
|
|||
}
|
||||
}, [currentTrack?.url]);
|
||||
|
||||
// Media Session API (Lock Screen Controls)
|
||||
useEffect(() => {
|
||||
if (!currentTrack || !('mediaSession' in navigator)) return;
|
||||
|
||||
navigator.mediaSession.metadata = new MediaMetadata({
|
||||
title: currentTrack.title,
|
||||
artist: currentTrack.artist,
|
||||
album: currentTrack.album,
|
||||
artwork: [
|
||||
{ src: currentTrack.cover_url, sizes: '96x96', type: 'image/jpeg' },
|
||||
{ src: currentTrack.cover_url, sizes: '128x128', type: 'image/jpeg' },
|
||||
{ src: currentTrack.cover_url, sizes: '192x192', type: 'image/jpeg' },
|
||||
{ src: currentTrack.cover_url, sizes: '256x256', type: 'image/jpeg' },
|
||||
{ src: currentTrack.cover_url, sizes: '384x384', type: 'image/jpeg' },
|
||||
{ src: currentTrack.cover_url, sizes: '512x512', type: 'image/jpeg' },
|
||||
]
|
||||
});
|
||||
|
||||
// Action Handlers
|
||||
navigator.mediaSession.setActionHandler('play', () => {
|
||||
togglePlay();
|
||||
navigator.mediaSession.playbackState = "playing";
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('pause', () => {
|
||||
togglePlay();
|
||||
navigator.mediaSession.playbackState = "paused";
|
||||
});
|
||||
navigator.mediaSession.setActionHandler('previoustrack', () => prevTrack());
|
||||
navigator.mediaSession.setActionHandler('nexttrack', () => nextTrack());
|
||||
navigator.mediaSession.setActionHandler('seekto', (details) => {
|
||||
if (details.seekTime !== undefined && audioRef.current) {
|
||||
audioRef.current.currentTime = details.seekTime;
|
||||
setProgress(details.seekTime);
|
||||
}
|
||||
});
|
||||
|
||||
}, [currentTrack]); // access to togglePlay etc. via closure is safe from context
|
||||
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
if (isPlaying) audioRef.current.play().catch(e => console.error("Play error:", e));
|
||||
else audioRef.current.pause();
|
||||
if (isPlaying) {
|
||||
audioRef.current.play().catch(e => console.error("Play error:", e));
|
||||
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = "playing";
|
||||
} else {
|
||||
audioRef.current.pause();
|
||||
if ('mediaSession' in navigator) navigator.mediaSession.playbackState = "paused";
|
||||
}
|
||||
}
|
||||
}, [isPlaying]);
|
||||
|
||||
|
|
@ -58,6 +101,20 @@ export default function PlayerBar() {
|
|||
if (!isNaN(audioRef.current.duration)) {
|
||||
setDuration(audioRef.current.duration);
|
||||
}
|
||||
|
||||
// Update Position State for standard progress bar on lock screen
|
||||
// Throttle this in real apps, but for simplicity:
|
||||
if ('mediaSession' in navigator && !isNaN(audioRef.current.duration)) {
|
||||
try {
|
||||
navigator.mediaSession.setPositionState({
|
||||
duration: audioRef.current.duration,
|
||||
playbackRate: audioRef.current.playbackRate,
|
||||
position: audioRef.current.currentTime
|
||||
});
|
||||
} catch (e) {
|
||||
// Ignore errors (often due to duration being infinite/NaN at start)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue