Skip to content

Commit bafb390

Browse files
committed
feat: add native lock-screen media controls for audio player
1 parent 0fc9b92 commit bafb390

1 file changed

Lines changed: 69 additions & 36 deletions

File tree

web/src/context/AudioContext.tsx

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client';
22

3-
import React, { createContext, useContext, useState, useRef, useEffect } from 'react';
3+
import React, { createContext, useContext, useState, useRef, useEffect, useCallback } from 'react';
44

55
export interface Track {
66
id: number;
@@ -32,6 +32,45 @@ export function AudioProvider({ children, allTracks }: { children: React.ReactNo
3232
const [progress, setProgress] = useState(0);
3333
const audioRef = useRef<HTMLAudioElement | null>(null);
3434

35+
const playTrack = useCallback((track: Track) => {
36+
if (audioRef.current) {
37+
setIsLoading(true); // Set loading immediately
38+
audioRef.current.src = track.url;
39+
audioRef.current.play().catch(e => {
40+
console.error("Playback failed:", e);
41+
setIsLoading(false);
42+
});
43+
setCurrentTrack(track);
44+
setIsPlaying(true);
45+
}
46+
}, []);
47+
48+
const togglePlay = useCallback(() => {
49+
if (audioRef.current) {
50+
if (isPlaying) {
51+
audioRef.current.pause();
52+
setIsPlaying(false);
53+
} else {
54+
audioRef.current.play().catch(console.error);
55+
setIsPlaying(true);
56+
}
57+
}
58+
}, [isPlaying]);
59+
60+
const playNext = useCallback(() => {
61+
if (!currentTrack) return;
62+
const currentIndex = allTracks.findIndex(t => t.id === currentTrack.id);
63+
const nextIndex = (currentIndex + 1) % allTracks.length;
64+
playTrack(allTracks[nextIndex]);
65+
}, [allTracks, currentTrack, playTrack]);
66+
67+
const playPrev = useCallback(() => {
68+
if (!currentTrack) return;
69+
const currentIndex = allTracks.findIndex(t => t.id === currentTrack.id);
70+
const prevIndex = (currentIndex - 1 + allTracks.length) % allTracks.length;
71+
playTrack(allTracks[prevIndex]);
72+
}, [allTracks, currentTrack, playTrack]);
73+
3574
// Initialize Audio
3675
useEffect(() => {
3776
audioRef.current = new Audio();
@@ -64,46 +103,40 @@ export function AudioProvider({ children, allTracks }: { children: React.ReactNo
64103
audioRef.current?.removeEventListener('playing', handlePlaying);
65104
audioRef.current?.removeEventListener('loadstart', handleLoadStart);
66105
};
67-
}, []);
106+
}, [playNext]);
68107

69-
const playTrack = (track: Track) => {
70-
if (audioRef.current) {
71-
setIsLoading(true); // Set loading immediately
72-
audioRef.current.src = track.url;
73-
audioRef.current.play().catch(e => {
74-
console.error("Playback failed:", e);
75-
setIsLoading(false);
108+
// Media Session Support (Lock Screen Controls)
109+
useEffect(() => {
110+
if (typeof window !== 'undefined' && 'mediaSession' in navigator && currentTrack) {
111+
navigator.mediaSession.metadata = new MediaMetadata({
112+
title: currentTrack.title,
113+
artist: currentTrack.singer && currentTrack.singer !== 'Unknown' ? currentTrack.singer : 'Sai Rahasya',
114+
album: currentTrack.category,
115+
artwork: [
116+
{ src: '/app-icon.png', sizes: '512x512', type: 'image/png' },
117+
]
76118
});
77-
setCurrentTrack(track);
78-
setIsPlaying(true);
79-
}
80-
};
81119

82-
const togglePlay = () => {
83-
if (audioRef.current) {
84-
if (isPlaying) {
85-
audioRef.current.pause();
86-
setIsPlaying(false);
87-
} else {
88-
audioRef.current.play().catch(console.error);
89-
setIsPlaying(true);
90-
}
120+
navigator.mediaSession.setActionHandler('play', togglePlay);
121+
navigator.mediaSession.setActionHandler('pause', togglePlay);
122+
navigator.mediaSession.setActionHandler('previoustrack', playPrev);
123+
navigator.mediaSession.setActionHandler('nexttrack', playNext);
124+
125+
return () => {
126+
navigator.mediaSession.setActionHandler('play', null);
127+
navigator.mediaSession.setActionHandler('pause', null);
128+
navigator.mediaSession.setActionHandler('previoustrack', null);
129+
navigator.mediaSession.setActionHandler('nexttrack', null);
130+
};
91131
}
92-
};
93-
94-
const playNext = () => {
95-
if (!currentTrack) return;
96-
const currentIndex = allTracks.findIndex(t => t.id === currentTrack.id);
97-
const nextIndex = (currentIndex + 1) % allTracks.length;
98-
playTrack(allTracks[nextIndex]);
99-
};
132+
}, [currentTrack, playNext, playPrev, togglePlay]);
100133

101-
const playPrev = () => {
102-
if (!currentTrack) return;
103-
const currentIndex = allTracks.findIndex(t => t.id === currentTrack.id);
104-
const prevIndex = (currentIndex - 1 + allTracks.length) % allTracks.length;
105-
playTrack(allTracks[prevIndex]);
106-
};
134+
// Sync playback state with Media Session
135+
useEffect(() => {
136+
if (typeof window !== 'undefined' && 'mediaSession' in navigator) {
137+
navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused';
138+
}
139+
}, [isPlaying]);
107140

108141
const closePlayer = () => {
109142
if (audioRef.current) {

0 commit comments

Comments
 (0)