Last active
January 26, 2024 09:40
-
-
Save sunmeat/319e4395b547869db334cd9f5619d2ba to your computer and use it in GitHub Desktop.
simple mp3-player android (service example)
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
MainActivity.java: (CREATE NEW EMPTY ACTIVITY PROJECT!) | |
package com.alex.player; | |
import package com.alex.player.MusicService.MusicBinder; | |
import android.content.Intent; | |
import android.os.Bundle; | |
import android.view.Menu; | |
import android.view.View; | |
import android.os.IBinder; | |
import android.content.ComponentName; | |
import android.content.Context; | |
import android.content.ServiceConnection; | |
import android.view.MenuItem; | |
import java.util.ArrayList; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import android.net.Uri; | |
import android.content.ContentResolver; | |
import android.database.Cursor; | |
import android.widget.ListView; | |
import android.widget.MediaController; | |
import android.widget.MediaController.MediaPlayerControl; | |
import com.karumi.dexter.Dexter; | |
import com.karumi.dexter.MultiplePermissionsReport; | |
import com.karumi.dexter.PermissionToken; | |
import com.karumi.dexter.listener.PermissionRequest; | |
import com.karumi.dexter.listener.multi.MultiplePermissionsListener; | |
public class MainActivity extends AppCompatActivity implements MediaPlayerControl { | |
private boolean paused; | |
private boolean playbackPaused; | |
private MediaController controller; | |
private MusicService musicSrv; | |
private Intent playIntent; | |
private boolean musicBound = false; | |
private ArrayList<Song> songList; | |
@Override | |
protected void onPause() { | |
super.onPause(); | |
paused = true; | |
} | |
@Override | |
protected void onResume() { | |
super.onResume(); | |
if (paused) { | |
setController(); | |
paused = false; | |
} | |
} | |
@Override | |
protected void onStop() { | |
controller.hide(); | |
super.onStop(); | |
} | |
private void setController() { | |
controller = new MediaController(this); | |
controller.setPrevNextListeners(new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
playNext(); | |
} | |
}, new View.OnClickListener() { | |
@Override | |
public void onClick(View v) { | |
playPrev(); | |
} | |
}); | |
controller.setMediaPlayer(this); | |
controller.setAnchorView(findViewById(R.id.song_list)); | |
controller.setEnabled(true); | |
} | |
@Override | |
public void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
// https://github.com/Karumi/Dexter | |
Dexter.withActivity(this) | |
.withPermissions(Manifest.permission.READ_EXTERNAL_STORAGE, | |
Manifest.permission.WAKE_LOCK) | |
.withListener(new MultiplePermissionsListener() { | |
@Override | |
public void onPermissionsChecked(MultiplePermissionsReport report) { | |
// Toast.makeText(MainActivity.this, "OK", Toast.LENGTH_SHORT).show(); | |
} | |
@Override | |
public void onPermissionRationaleShouldBeShown(List<PermissionRequest> permissions, PermissionToken token) { | |
} | |
}).check(); | |
ListView songView = findViewById(R.id.song_list); | |
songList = new ArrayList<>(); | |
getSongList(); | |
Collections.sort(songList, new Comparator<Song>() { | |
public int compare(Song a, Song b) { | |
return a.getTitle().compareTo(b.getTitle()); | |
} | |
}); | |
SongAdapter songAdt = new SongAdapter(this, songList); | |
songView.setAdapter(songAdt); | |
setController(); | |
} | |
private void playNext() { | |
musicSrv.playNext(); | |
if (playbackPaused) { | |
setController(); | |
playbackPaused = false; | |
} | |
controller.show(0); | |
} | |
private void playPrev() { | |
musicSrv.playPrev(); | |
if (playbackPaused) { | |
setController(); | |
playbackPaused = false; | |
} | |
controller.show(0); | |
} | |
public void getSongList() { | |
ContentResolver musicResolver = getContentResolver(); | |
Uri musicUri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; | |
Cursor musicCursor = musicResolver.query(musicUri, null, null, null, null); | |
if (musicCursor != null && musicCursor.moveToFirst()) { | |
int titleColumn = musicCursor.getColumnIndex | |
(android.provider.MediaStore.Audio.Media.TITLE); | |
int idColumn = musicCursor.getColumnIndex | |
(android.provider.MediaStore.Audio.Media._ID); | |
int artistColumn = musicCursor.getColumnIndex | |
(android.provider.MediaStore.Audio.Media.ARTIST); | |
do { | |
long thisId = musicCursor.getLong(idColumn); | |
String thisTitle = musicCursor.getString(titleColumn); | |
String thisArtist = musicCursor.getString(artistColumn); | |
songList.add(new Song(thisId, thisTitle, thisArtist)); | |
} | |
while (musicCursor.moveToNext()); | |
} | |
} | |
private ServiceConnection musicConnection = new ServiceConnection() { | |
@Override | |
public void onServiceConnected(ComponentName name, IBinder service) { | |
MusicBinder binder = (MusicBinder) service; | |
musicSrv = binder.getService(); | |
musicSrv.setList(songList); | |
musicBound = true; | |
} | |
@Override | |
public void onServiceDisconnected(ComponentName name) { | |
musicBound = false; | |
} | |
}; | |
public void songPicked(View view) { | |
musicSrv.setSong(Integer.parseInt(view.getTag().toString())); | |
musicSrv.playSong(); | |
if (playbackPaused) { | |
setController(); | |
playbackPaused = false; | |
} | |
controller.show(0); | |
} | |
@Override | |
public boolean onOptionsItemSelected(MenuItem item) { | |
switch (item.getItemId()) { | |
case R.id.action_shuffle: | |
musicSrv.setShuffle(); | |
break; | |
case R.id.action_end: | |
stopService(playIntent); | |
musicSrv = null; | |
System.exit(0); | |
break; | |
} | |
return super.onOptionsItemSelected(item); | |
} | |
@Override | |
public boolean onCreateOptionsMenu(Menu menu) { | |
getMenuInflater().inflate(R.menu.menu_main, menu); | |
return true; | |
} | |
@Override | |
protected void onStart() { | |
super.onStart(); | |
if (playIntent == null) { | |
playIntent = new Intent(this, MusicService.class); | |
bindService(playIntent, musicConnection, Context.BIND_AUTO_CREATE); | |
startService(playIntent); | |
} | |
} | |
@Override | |
protected void onDestroy() { | |
stopService(playIntent); | |
musicSrv = null; | |
super.onDestroy(); | |
} | |
@Override | |
public void pause() { | |
playbackPaused = true; | |
musicSrv.pausePlayer(); | |
} | |
@Override | |
public void seekTo(int pos) { | |
musicSrv.seek(pos); | |
} | |
@Override | |
public void start() { | |
musicSrv.go(); | |
} | |
@Override | |
public int getDuration() { | |
if (musicSrv != null && musicBound && musicSrv.isPng()) | |
return musicSrv.getDur(); | |
else return 0; | |
} | |
@Override | |
public int getCurrentPosition() { | |
if (musicSrv != null && musicBound && musicSrv.isPng()) | |
return musicSrv.getPosn(); | |
else return 0; | |
} | |
@Override | |
public boolean isPlaying() { | |
return musicSrv != null && musicBound && musicSrv.isPng(); | |
} | |
@Override | |
public int getBufferPercentage() { | |
return 0; | |
} | |
@Override | |
public boolean canPause() { | |
return true; | |
} | |
@Override | |
public boolean canSeekBackward() { | |
return true; | |
} | |
@Override | |
public boolean canSeekForward() { | |
return true; | |
} | |
@Override | |
public int getAudioSessionId() { | |
return 0; | |
} | |
} | |
================================================================================================================================== | |
MusicService.java: | |
package com.alex.player; | |
import android.app.Service; | |
import android.content.Intent; | |
import android.media.MediaPlayer; | |
import android.os.IBinder; | |
import java.util.ArrayList; | |
import android.content.ContentUris; | |
import android.media.AudioManager; | |
import android.net.Uri; | |
import android.os.Binder; | |
import android.os.PowerManager; | |
import android.util.Log; | |
import java.util.Random; | |
import android.app.Notification; | |
import android.app.PendingIntent; | |
public class MusicService extends Service implements MediaPlayer.OnPreparedListener, MediaPlayer.OnErrorListener, | |
MediaPlayer.OnCompletionListener { | |
private boolean shuffle = false; | |
private Random rand; | |
private String songTitle = ""; | |
private static final int NOTIFY_ID = 1; | |
private MediaPlayer player; | |
private ArrayList<Song> songs; | |
private int songPosn; | |
private final IBinder musicBind = new MusicBinder(); | |
public void setShuffle() { | |
shuffle = !shuffle; | |
} | |
public void onCreate() { | |
super.onCreate(); | |
rand = new Random(); | |
songPosn = 0; | |
player = new MediaPlayer(); | |
initMusicPlayer(); | |
} | |
public void initMusicPlayer() { | |
player.setWakeMode(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK); | |
player.setAudioStreamType(AudioManager.STREAM_MUSIC); | |
player.setOnPreparedListener(this); | |
player.setOnCompletionListener(this); | |
player.setOnErrorListener(this); | |
} | |
public void setList(ArrayList<Song> theSongs) { | |
songs = theSongs; | |
} | |
public class MusicBinder extends Binder { | |
MusicService getService() { | |
return MusicService.this; | |
} | |
} | |
@Override | |
public IBinder onBind(Intent intent) { | |
return musicBind; | |
} | |
@Override | |
public boolean onUnbind(Intent intent) { | |
player.stop(); | |
player.release(); | |
return false; | |
} | |
public void playSong() { | |
player.reset(); | |
Song playSong = songs.get(songPosn); | |
songTitle = playSong.getTitle(); | |
long currSong = playSong.getID(); | |
Uri trackUri = ContentUris.withAppendedId( | |
android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, | |
currSong); | |
try { | |
player.setDataSource(getApplicationContext(), trackUri); | |
} catch (Exception e) { | |
Log.e("MUSIC SERVICE", "Error setting data source", e); | |
} | |
player.prepareAsync(); | |
} | |
@Override | |
public void onDestroy() { | |
stopForeground(true); | |
} | |
@Override | |
public void onCompletion(MediaPlayer mp) { | |
if (player.getCurrentPosition() > 0) { | |
mp.reset(); | |
playNext(); | |
} | |
} | |
@Override | |
public boolean onError(MediaPlayer mp, int what, int extra) { | |
mp.reset(); | |
return false; | |
} | |
public void setSong(int songIndex) { | |
songPosn = songIndex; | |
} | |
@Override | |
public void onPrepared(MediaPlayer mp) { | |
mp.start(); | |
Intent notIntent = new Intent(this, MainActivity.class); | |
notIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); | |
PendingIntent pendInt = PendingIntent.getActivity(this, 0, notIntent, PendingIntent.FLAG_UPDATE_CURRENT); | |
Notification.Builder builder = new Notification.Builder(this); | |
builder.setContentIntent(pendInt) | |
.setSmallIcon(R.drawable.play) | |
.setTicker(songTitle) | |
.setOngoing(true) | |
.setContentTitle("Playing") | |
.setContentText(songTitle); | |
Notification not = builder.build(); | |
startForeground(NOTIFY_ID, not); | |
} | |
public int getPosn() { | |
return player.getCurrentPosition(); | |
} | |
public int getDur() { | |
return player.getDuration(); | |
} | |
public boolean isPng() { | |
return player.isPlaying(); | |
} | |
public void pausePlayer() { | |
player.pause(); | |
} | |
public void seek(int posn) { | |
player.seekTo(posn); | |
} | |
public void go() { | |
player.start(); | |
} | |
public void playPrev() { | |
songPosn--; | |
if (songPosn < 0) songPosn = songs.size() - 1; | |
playSong(); | |
} | |
public void playNext() { | |
if (shuffle) { | |
int newSong = songPosn; | |
while (newSong == songPosn) { | |
newSong = rand.nextInt(songs.size()); | |
} | |
songPosn = newSong; | |
} else { | |
songPosn++; | |
if (songPosn >= songs.size()) songPosn = 0; | |
} | |
playSong(); | |
} | |
} | |
================================================================================================================================== | |
Song.java: | |
package com.alex.player; | |
class Song { | |
private long id; | |
private String title; | |
private String artist; | |
Song(long songID, String songTitle, String songArtist) { | |
id = songID; | |
title = songTitle; | |
artist = songArtist; | |
} | |
long getID() { | |
return id; | |
} | |
String getTitle() { | |
return title; | |
} | |
String getArtist() { | |
return artist; | |
} | |
} | |
================================================================================================================================== | |
SongAdapter.java: | |
package com.alex.player; | |
import java.util.ArrayList; | |
import android.content.Context; | |
import android.view.LayoutInflater; | |
import android.view.View; | |
import android.view.ViewGroup; | |
import android.widget.LinearLayout; | |
import android.widget.TextView; | |
import android.widget.BaseAdapter; | |
class SongAdapter extends BaseAdapter { | |
private ArrayList<Song> songs; | |
private LayoutInflater songInf; | |
SongAdapter(Context c, ArrayList<Song> theSongs) { | |
songs = theSongs; | |
songInf = LayoutInflater.from(c); | |
} | |
@Override | |
public int getCount() { | |
return songs.size(); | |
} | |
@Override | |
public Object getItem(int arg0) { | |
return null; | |
} | |
@Override | |
public long getItemId(int arg0) { | |
return 0; | |
} | |
@Override | |
public View getView(int position, View convertView, ViewGroup parent) { | |
LinearLayout songLay = (LinearLayout) songInf.inflate | |
(R.layout.song, parent, false); | |
TextView songView = (TextView) songLay.findViewById(R.id.song_title); | |
TextView artistView = (TextView) songLay.findViewById(R.id.song_artist); | |
Song currSong = songs.get(position); | |
songView.setText(currSong.getTitle()); | |
artistView.setText(currSong.getArtist()); | |
songLay.setTag(position); | |
return songLay; | |
} | |
} | |
================================================================================================================================== | |
drawable: end, play, rand | |
================================================================================================================================== | |
layout / activity_main.xml: | |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:tools="http://schemas.android.com/tools" | |
android:layout_width="fill_parent" | |
android:layout_height="fill_parent" | |
android:background="#FF000000" | |
android:orientation="vertical" | |
tools:context=".MainActivity"> | |
<ListView | |
android:id="@+id/song_list" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" /> | |
</LinearLayout> | |
================================================================================================================================== | |
layout / song.xml: | |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:onClick="songPicked" | |
android:orientation="vertical" | |
android:padding="5dp" > | |
<TextView | |
android:id="@+id/song_title" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:textColor="#FFFFFFFF" | |
android:textSize="20sp" | |
android:textStyle="bold" /> | |
<TextView | |
android:id="@+id/song_artist" | |
android:layout_width="match_parent" | |
android:layout_height="wrap_content" | |
android:textColor="#FFAAAAAA" | |
android:textSize="18sp" /> | |
</LinearLayout> | |
================================================================================================================================== | |
menu / menu_main.xml: | |
<menu xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto"> | |
<item | |
android:id="@+id/action_shuffle" | |
android:icon="@drawable/rand" | |
android:orderInCategory="1" | |
app:showAsAction="ifRoom" | |
android:title="Shuffle"/> | |
<item | |
android:id="@+id/action_end" | |
android:icon="@drawable/end" | |
android:orderInCategory="2" | |
app:showAsAction="ifRoom" | |
android:title="End"/> | |
</menu> | |
================================================================================================================================== | |
AndroidManifest.xml: (check service!) | |
... | |
<uses-permission android:name="android.permission.WAKE_LOCK" /> | |
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | |
... | |
================================================================================================================================== | |
build.gradle (Module:app): | |
dependencies { | |
... | |
implementation 'com.karumi:dexter:5.0.0' | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment