Abstracted the MediaPlayer from the MainActivity and turned it into a service that has the ability to run regardless if an acitivity is present #28. Resolved issue where track display details was blank after switching from the settings view #26

This commit is contained in:
amazing-username
2019-03-10 00:43:03 -05:00
committed by amazing-username
parent 971da4b9d2
commit 040544e630
10 changed files with 452 additions and 147 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
+3 -4
View File
@@ -15,15 +15,12 @@
android:name=".activities.SettingsActivity"
android:label="@string/title_activity_settings"
android:parentActivityName=".activities.MainActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.mear.activities.MainActivity" />
</activity>
<activity
android:name=".activities.fdh"
android:label="@string/title_activity_settings"
android:screenOrientation="portrait"></activity>
<activity
android:name=".activities.MainActivity"
android:label="@string/title_activity_main"
@@ -35,6 +32,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".playback.service.MusicService" />
</application>
</manifest>
@@ -1,50 +1,91 @@
package com.example.mear.activities
import android.graphics.Color
import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import android.graphics.BitmapFactory
import android.media.MediaPlayer
import android.graphics.Color
import android.media.MediaMetadataRetriever
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.support.v7.app.AppCompatActivity
import android.widget.Toast
import java.lang.Exception
import java.lang.Runnable
import java.util.concurrent.TimeUnit
import kotlin.io.*
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_play_controls.*
import kotlinx.android.synthetic.main.fragment_track_cover.*
import kotlinx.android.synthetic.main.fragment_track_details.*
import kotlinx.android.synthetic.main.fragment_track_elapsing.*
import kotlinx.android.synthetic.main.fragment_track_flow.*
import kotlin.io.*
import kotlin.random.Random
import org.jetbrains.anko.image
import org.jetbrains.anko.imageBitmap
import com.example.mear.playback.service.MusicService
import com.example.mear.R
import com.example.mear.repositories.TrackRepository
import com.example.mear.models.PlayControls
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class MainActivity : AppCompatActivity() {
private var musicService: MusicService? = null
private var serviceBinded: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
initialize()
}
override fun onStop() {
super.onStop()
try {
if (serviceBinded == null || serviceBinded!! == false) {
unbindService(mConnection)
serviceBinded = true
}
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
override fun onDestroy() {
super.onDestroy()
try {
if (serviceBinded!!.equals(null) || serviceBinded!! == false ) {
unbindService(mConnection)
serviceBinded = true
}
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
private fun initialize() {
songCount = TrackRepository(this).getSongCount()
currentSong = fetchSongIndex(PlayTypes.PlaySong)
TrackElapsing.progress = 0
TrackElapsing.max = 100
initializeMediaPlayer()
try {
initializeServices()
initializeClickListeners()
initializeCompletionListener()
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
private fun initializeClickListeners() {
PlayTrack.setOnClickListener {
@@ -62,8 +103,6 @@ class MainActivity : AppCompatActivity() {
SettingsLink.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
try {
startActivity(intent)
}
catch (ex: Exception) {
@@ -72,19 +111,8 @@ class MainActivity : AppCompatActivity() {
}
}
}
private fun initializeCompletionListener() {
trackPlayer!!.setOnCompletionListener {
playNextSongTrack()
}
}
private fun initializeMediaPlayer() {
if (trackPlayer == null) {
trackPlayer = MediaPlayer()
playerInitialized = true
val tr = TrackRepository(this).getTrack(currentSong!!)
trackPlayer!!.setDataSource(tr.songPath)
trackPlayer!!.prepare()
}
private fun initializeServices() {
doBindService()
}
private fun toggleShuffle() {
@@ -108,12 +136,12 @@ class MainActivity : AppCompatActivity() {
}
private fun playSongTrack() {
PlayTrack.isEnabled = true
PlayTrack.isEnabled = false
try {
if (!trackPlayer!!.isPlaying) {
trackPlayer!!.start()
if (!musicService!!.isPlaying()) {
musicService!!.playSongTrack()
} else {
trackPlayer!!.pause()
musicService!!.pauseSongTrack()
}
configureTrackDisplay()
}
@@ -125,16 +153,9 @@ class MainActivity : AppCompatActivity() {
private fun playNextSongTrack() {
NextTrack.isEnabled = false
try {
currentSong = fetchSongIndex(PlayTypes.PlayNextSong)
val controls = PlayControls(shuffleOn!!)
musicService!!.playNextTrack(controls)
if (trackPlayer!!.isPlaying) {
trackPlayer!!.stop()
}
trackPlayer!!.reset()
trackPlayer!!.setDataSource(TrackRepository(this).getTrack(currentSong!!).songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
configureTrackDisplay()
}
catch (ex: Exception) {
@@ -146,16 +167,9 @@ class MainActivity : AppCompatActivity() {
private fun playPreviousSongTrack() {
PreviousTrack.isEnabled = false
try {
currentSong = fetchSongIndex(PlayTypes.PlayPreviousSong)
val controls = PlayControls(shuffleOn!!)
musicService!!.playPreviousTrack(controls)
if (trackPlayer!!.isPlaying) {
trackPlayer!!.stop()
}
trackPlayer!!.reset()
trackPlayer!!.setDataSource(TrackRepository(this).getTrack(currentSong!!).songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
configureTrackDisplay()
}
catch (ex: Exception) {
@@ -166,8 +180,9 @@ class MainActivity : AppCompatActivity() {
}
private fun configureTrackDisplay() {
try {
runOnUiThread {
configurePlayControlsDisplay()
val currTrack = TrackRepository(this).getTrack(currentSong!!)
val currTrack = musicService!!.getCurrentTrack()
val trackTitle = currTrack.title
val artistTitle = currTrack.artist
val albumTitle = currTrack.album
@@ -186,11 +201,8 @@ class MainActivity : AppCompatActivity() {
}
updateTrackProgress()
TrackTitle.text = null
ArtistTitle.text = null
AlbumTitle.text = null
CurrentPosition.text = null
TrackCover.setImageBitmap(null)
resetControls()
TrackTitle.text = trackTitle
ArtistTitle.text = artistTitle
@@ -199,21 +211,29 @@ class MainActivity : AppCompatActivity() {
if (trackCover != null) {
val songImage = BitmapFactory
.decodeByteArray(trackCover, 0, trackCover.size)
TrackCover.setImageBitmap(songImage)
TrackCover.imageBitmap = songImage
}
}
}
catch (ex: Exception) {
val msg = ex.message
}
}
private fun resetControls() {
TrackTitle!!.text = null
ArtistTitle!!.text = null
AlbumTitle!!.text = null
CurrentPosition!!.text = null
TrackCover!!.imageBitmap = null
}
private fun updateTrackProgress() {
musicHandler!!.postDelayed(musicTrackTimeUpdateTask, 100)
musicHandler!!.postDelayed(musicTrackTimeUpdateTask, 250)
}
private fun configurePlayControlsDisplay() {
PlayTrack.background = null
PlayTrack.colorFilter = null
if (!trackPlayer!!.isPlaying) {
if (!musicService!!.isPlaying()) {
PlayTrack.setImageResource(android.R.drawable.ic_media_pause)
PlayTrack.setColorFilter(Color.RED)
}
@@ -223,51 +243,25 @@ class MainActivity : AppCompatActivity() {
}
}
private fun fetchSongIndex(playType: PlayTypes): Int {
var songIndex: Int? = null
try {
when (playType) {
PlayTypes.PlayPreviousSong -> {
songIndex = songCount
if (currentSong!! != 0) {
if (!shuffleOn!!) {
songIndex = currentSong!!.dec()
}
else {
songIndex = Random.nextInt(0, songCount!!)
}
}
}
PlayTypes.PlaySong -> {
songIndex = Random.nextInt(0, songCount!!)
}
PlayTypes.PlayNextSong -> {
songIndex = 0
if (currentSong!! != songCount!!) {
if (!shuffleOn!!) {
songIndex = currentSong!!.inc()
}
else {
songIndex = Random.nextInt(0, songCount!!)
}
}
}
}
}
catch (ex: Exception) {
val exMsg = ex.message
println(exMsg)
}
private fun isServiceRunning(serviceClass: Class<*>): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
return songIndex!!
for (service in activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
return true
}
}
return false
}
private var musicTrackTimeUpdateTask = object: Runnable {
override fun run() {
try {
var newPosition = 0
val currentPosition = trackPlayer!!.currentPosition / 1000
val totalDuration = trackPlayer!!.duration / 1000
val currentPosition = musicService!!.currentPositionOfTrack() / 1000
val totalDuration = musicService!!.durationOfTrack() / 1000
newPosition = (((currentPosition).toDouble() / totalDuration) * 100).toInt()
val dur = String.format(
"%02d:%02d", TimeUnit.SECONDS.toMinutes(currentPosition.toLong()),
@@ -277,18 +271,69 @@ class MainActivity : AppCompatActivity() {
TrackElapsing.progress = newPosition
musicHandler!!.postDelayed(this, 100)
if (TrackCover.image == null && musicService!!.isPlaying()) {
configureTrackDisplay()
}
musicHandler!!.postDelayed(this, 250)
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
}
private val mConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
resetControls()
runBlocking {
val demo = launch {
musicService = (service as MusicService.LocalBinder).service
}
demo.start()
}
if (musicService != null) {
if (musicService!!.isPlaying()) {
configureTrackDisplay()
}
}
Toast.makeText(
this@MainActivity, "Music Service Started",
Toast.LENGTH_SHORT
).show()
}
override fun onServiceDisconnected(className: ComponentName) {
resetControls()
Toast.makeText(
this@MainActivity, "Music Service Stopped",
Toast.LENGTH_SHORT
).show()
}
}
fun doBindService() {
val intent = Intent(this, MusicService::class.java)
if (isServiceRunning(MusicService::class.java)) {
val suc = "Service is already running"
}
else {
startService(intent)
}
var result = bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
if (result) {
}
else {
}
}
private var musicHandler: Handler? = Handler()
private var trackPlayer: MediaPlayer? = null
private var currentSong: Int? = null
private var playerInitialized: Boolean? = false
private var shuffleOn: Boolean? = false
private var songCount: Int? = null
private enum class PlayTypes {
PlayNextSong, PlaySong, PlayPreviousSong
}
}
@@ -42,6 +42,7 @@ class SettingsActivity : AppCompatActivity() {
try {
TrackRepository(this).delete()
val trackMgr = TrackManager(allSongs)
trackMgr.configureTracks(this)
}
catch (ex: Exception) {
val exMsg = ex.message
@@ -0,0 +1,7 @@
package com.example.mear.constants
object DirectoryIgnore {
const val Android_Root = "Android/"
const val Notifications = "Notifications/"
const val Ringtones = "Ringtones/"
}
@@ -0,0 +1,5 @@
package com.example.mear.constants
object FileTypes {
const val Mp3 = "mp3"
}
@@ -0,0 +1,4 @@
package com.example.mear.models
data class PlayControls(val shuffleOn: Boolean) {
}
@@ -1,45 +1,274 @@
package com.example.mear.playback.service
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.os.Binder
import android.os.IBinder
import android.R
import android.R.string.cancel
import android.os.Looper
import android.widget.Toast
import java.lang.Exception
import kotlin.random.Random
import com.example.mear.models.Track
import com.example.mear.management.DatabaseManager
import com.example.mear.management.MusicFiles
import com.example.mear.management.TrackManager
import com.example.mear.models.PlayControls
import com.example.mear.models.Track
import com.example.mear.repositories.TrackRepository
class MusicService: Service() {
private var trackPlayer: MediaPlayer? = null
private var currentSongIndex: Int? = null
private var shuffleOn: Boolean? = false
private val mBinder = LocalBinder()
private val seconds = 4
inner class LocalBinder : Binder() {
internal val service: MusicService
get() = this@MusicService
}
private enum class PlayTypes {
PlayNextSong, PlaySong, PlayPreviousSong
}
override fun onCreate() {
initializeMediaPlayer()
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return Service.START_NOT_STICKY
return Service.START_STICKY
}
override fun onDestroy() {
val intent = Intent(this, mBinder.javaClass)
onUnbind(intent)
Toast.makeText(this, "Music Service Stopped", Toast.LENGTH_SHORT).show()
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
override fun onUnbind(intent: Intent?): Boolean {
return super.onUnbind(intent)
}
fun playSongTrack() {
try {
trackPlayer!!.start()
}
catch (ex: Exception ) {
val exMsg = ex.message
}
}
fun pauseSongTrack() {
try {
trackPlayer!!.pause()
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
fun playPreviousTrack() {
try {
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
fun playNextTrack() {
try {
trackPlayer!!.reset()
var nextTrack = fetchSongIndex(PlayTypes.PlayNextSong)
val track = TrackRepository(this).getTrack(nextTrack)
trackPlayer!!.setDataSource(track.songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
fun playPreviousTrack(controls: PlayControls) {
try {
configureControl(controls)
val duration = trackPlayer!!.currentPosition / 1000
if (duration > seconds) {
trackPlayer!!.reset()
trackPlayer!!.setDataSource(TrackRepository(this).getTrack(currentSongIndex!!).songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
}
else {
var previousTrack = fetchSongIndex(PlayTypes.PlayPreviousSong)
val track = TrackRepository(this).getTrack(previousTrack)
trackPlayer!!.reset()
trackPlayer!!.setDataSource(track.songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
}
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
fun configureControl(controls: PlayControls) {
shuffleOn = controls.shuffleOn
}
fun playNextTrack(controls: PlayControls) {
try {
configureControl(controls)
val nextTrack = fetchSongIndex(PlayTypes.PlayNextSong)
val track = TrackRepository(this).getTrack(nextTrack)
trackPlayer!!.reset()
trackPlayer!!.setDataSource(track.songPath)
trackPlayer!!.prepare()
trackPlayer!!.start()
}
catch (ex: Exception) {
val exMsg = ex.message
}
}
fun getCurrentTrack(): Track {
var track: Track? = null
track = TrackRepository(this).getTrack(currentSongIndex!!)
return track!!
}
private fun fetchSongIndex(playType: PlayTypes): Int {
var songIndex: Int? = currentSongIndex
val songCount = retrieveSongCount()
try {
when (playType) {
PlayTypes.PlayPreviousSong -> {
if (currentSongIndex!! != 0) {
if (!shuffleOn!!) {
currentSongIndex = currentSongIndex!!.dec()
songIndex = currentSongIndex!!
}
else {
currentSongIndex = Random.nextInt(0, songCount!!)
songIndex = currentSongIndex
}
}
}
PlayTypes.PlaySong -> {
if (shuffleOn!!) {
songIndex = Random.nextInt(0, songCount!!)
}
else {
songIndex = 0
}
}
PlayTypes.PlayNextSong -> {
if (currentSongIndex!! != songCount!!) {
if (!shuffleOn!!) {
currentSongIndex = currentSongIndex!!.inc()
songIndex = currentSongIndex!!
}
else {
currentSongIndex = Random.nextInt(0, songCount!!)
songIndex = currentSongIndex!!
}
}
}
}
}
catch (ex: Exception) {
val exMsg = ex.message
println(exMsg)
}
return songIndex!!
}
fun currentPositionOfTrack(): Int {
return trackPlayer!!.currentPosition
}
fun durationOfTrack(): Int {
return trackPlayer!!.duration
}
fun isPlaying(): Boolean {
return trackPlayer!!.isPlaying
}
private fun initializeMediaPlayer() {
if (trackPlayer == null) {
trackPlayer = MediaPlayer()
}
if (trackRepositoryEmpty()!!) {
populateTrackRepository()
}
currentSongIndex = Random.nextInt(0, TrackRepository(this).getSongCount())
val trackToPlay = TrackRepository(this).getTrack(currentSongIndex!!)
trackPlayer!!.setDataSource(trackToPlay.songPath)
trackPlayer!!.prepareAsync()
trackPlayer!!.setOnCompletionListener {
playNextTrack()
}
}
private fun populateTrackRepository() {
var mp3Paths = MusicFiles(android.os.Environment.getExternalStorageDirectory())
mp3Paths.searchForMp3Songs()
val paths = mp3Paths.allSongs
val trackMgr = TrackManager(paths!!)
trackMgr.configureTracks(this)
}
private fun trackRepositoryEmpty(): Boolean? {
val trackCount = retrieveSongCount()
when (trackCount) {
null -> {
return true
}
0 -> {
return true
}
}
return false
}
private fun retrieveSongCount(): Int? {
try {
var db = DatabaseManager(this)
var trackDb = TrackRepository(this)
val count = trackDb.getSongCount()
return count
}
catch (ex: Exception) {
val exMsg = ex.message
}
return 0
}
}
@@ -7,9 +7,15 @@ import org.jetbrains.anko.db.*
import com.example.mear.database
import com.example.mear.models.Track
class TrackRepository(val context: Context) {
class TrackRepository {
private var context: Context? = null
fun getAll(): List<Track> = context.database.use {
constructor(context: Context)
{
this.context = context
}
fun getAll(): List<Track> = context!!.database.use {
val tracks = mutableListOf<Track>()
select("Track" )
@@ -31,7 +37,7 @@ class TrackRepository(val context: Context) {
})
tracks
}
fun getTrack(id: Int): Track = context.database.use {
fun getTrack(id: Int): Track = context!!.database.use {
select("Track").where("Id = $id")
.parseSingle(object: MapRowParser<Track>{
override fun parseRow(columns: Map<String, Any?>): Track {
@@ -49,7 +55,7 @@ class TrackRepository(val context: Context) {
}
})
}
fun getSongCount(): Int = context.database.use {
fun getSongCount(): Int = context!!.database.use {
select("TrackCount").limit(1)
.parseSingle(object : MapRowParser<Int>{
override fun parseRow(columns: Map<String, Any?>): Int {
@@ -59,7 +65,7 @@ class TrackRepository(val context: Context) {
})
}
fun insertTrack(track: Track) = context.database.use {
fun insertTrack(track: Track) = context!!.database.use {
insert("Track",
"Id" to track.id,
"Title" to track.title,
@@ -68,16 +74,25 @@ class TrackRepository(val context: Context) {
"Duration" to track.length,
"FilePath" to track.songPath)
}
fun createSongCount(songCount: Int) = context.database.use {
fun createSongCount(songCount: Int) = context!!.database.use {
delete("TrackCount")
insert("TrackCount",
"Id" to 0,
"TotalSongs" to songCount)
}
fun delete(table: Any?) = context.database.use {
delete("Track", whereClause = "id = {$table.id}")
}
fun delete() = context.database.use {
fun delete() = context!!.database.use {
delete("Track")
}
fun getLibraryCount(): Int? {
context!!.database.use {
select("TrackCount").limit(1)
.parseList (object : MapRowParser<Int> {
override fun parseRow(columns: Map<String, Any?>): Int {
return columns.getValue("TotalSongs").toString().toInt()
}
})
}
return 0
}
}