diff --git a/.idea/misc.xml b/.idea/misc.xml index 7bfef59..37a7509 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3da38be..abc50de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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"> - + + \ No newline at end of file diff --git a/app/src/main/java/com/example/mear/activities/MainActivity.kt b/app/src/main/java/com/example/mear/activities/MainActivity.kt index 811b149..c775225 100644 --- a/app/src/main/java/com/example/mear/activities/MainActivity.kt +++ b/app/src/main/java/com/example/mear/activities/MainActivity.kt @@ -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() - initializeClickListeners() - initializeCompletionListener() + try { + initializeServices() + initializeClickListeners() + } + 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,54 +180,60 @@ class MainActivity : AppCompatActivity() { } private fun configureTrackDisplay() { try { - configurePlayControlsDisplay() - val currTrack = TrackRepository(this).getTrack(currentSong!!) - val trackTitle = currTrack.title - val artistTitle = currTrack.artist - val albumTitle = currTrack.album - val trackDuration = currTrack.length - var trackCover: ByteArray? = null - val dur = String.format( - "%02d:%02d", TimeUnit.SECONDS.toMinutes(trackDuration.toLong()), - (trackDuration % 60) - ) + runOnUiThread { + configurePlayControlsDisplay() + val currTrack = musicService!!.getCurrentTrack() + val trackTitle = currTrack.title + val artistTitle = currTrack.artist + val albumTitle = currTrack.album + val trackDuration = currTrack.length + var trackCover: ByteArray? = null + val dur = String.format( + "%02d:%02d", TimeUnit.SECONDS.toMinutes(trackDuration.toLong()), + (trackDuration % 60) + ) - val mmr = MediaMetadataRetriever() - mmr.setDataSource(currTrack.songPath) + val mmr = MediaMetadataRetriever() + mmr.setDataSource(currTrack.songPath) - if (mmr.embeddedPicture != null) { - trackCover = mmr.embeddedPicture - } - updateTrackProgress() + if (mmr.embeddedPicture != null) { + trackCover = mmr.embeddedPicture + } + updateTrackProgress() - TrackTitle.text = null - ArtistTitle.text = null - AlbumTitle.text = null - CurrentPosition.text = null - TrackCover.setImageBitmap(null) + resetControls() - TrackTitle.text = trackTitle - ArtistTitle.text = artistTitle - AlbumTitle.text = albumTitle - TrackDuration.text = dur - if (trackCover != null) { - val songImage = BitmapFactory - .decodeByteArray(trackCover, 0, trackCover.size) - TrackCover.setImageBitmap(songImage) + + TrackTitle.text = trackTitle + ArtistTitle.text = artistTitle + AlbumTitle.text = albumTitle + TrackDuration.text = dur + if (trackCover != null) { + val songImage = BitmapFactory + .decodeByteArray(trackCover, 0, trackCover.size) + 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!!) - } - } - } + private fun isServiceRunning(serviceClass: Class<*>): Boolean { + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + + for (service in activityManager.getRunningServices(Integer.MAX_VALUE)) { + if (serviceClass.name == service.service.className) { + return true } } - catch (ex: Exception) { - val exMsg = ex.message - println(exMsg) - } - - return songIndex!! + 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 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 val mConnection = object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + resetControls() - private enum class PlayTypes { - PlayNextSong, PlaySong, PlayPreviousSong + 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 shuffleOn: Boolean? = false + } diff --git a/app/src/main/java/com/example/mear/activities/SettingsActivity.kt b/app/src/main/java/com/example/mear/activities/SettingsActivity.kt index 773d9b2..e632e91 100644 --- a/app/src/main/java/com/example/mear/activities/SettingsActivity.kt +++ b/app/src/main/java/com/example/mear/activities/SettingsActivity.kt @@ -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 diff --git a/app/src/main/java/com/example/mear/constants/DirectoryIgnore.kt b/app/src/main/java/com/example/mear/constants/DirectoryIgnore.kt new file mode 100644 index 0000000..5978a7b --- /dev/null +++ b/app/src/main/java/com/example/mear/constants/DirectoryIgnore.kt @@ -0,0 +1,7 @@ +package com.example.mear.constants + +object DirectoryIgnore { + const val Android_Root = "Android/" + const val Notifications = "Notifications/" + const val Ringtones = "Ringtones/" +} diff --git a/app/src/main/java/com/example/mear/constants/FileTypes.kt b/app/src/main/java/com/example/mear/constants/FileTypes.kt new file mode 100644 index 0000000..4f21193 --- /dev/null +++ b/app/src/main/java/com/example/mear/constants/FileTypes.kt @@ -0,0 +1,5 @@ +package com.example.mear.constants + +object FileTypes { + const val Mp3 = "mp3" +} diff --git a/app/src/main/java/com/example/mear/models/PlayControls.kt b/app/src/main/java/com/example/mear/models/PlayControls.kt new file mode 100644 index 0000000..d019999 --- /dev/null +++ b/app/src/main/java/com/example/mear/models/PlayControls.kt @@ -0,0 +1,4 @@ +package com.example.mear.models + +data class PlayControls(val shuffleOn: Boolean) { +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mear/playback/service/MusicService.kt b/app/src/main/java/com/example/mear/playback/service/MusicService.kt index 0774b60..46cf891 100644 --- a/app/src/main/java/com/example/mear/playback/service/MusicService.kt +++ b/app/src/main/java/com/example/mear/playback/service/MusicService.kt @@ -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 + } } diff --git a/app/src/main/java/com/example/mear/repositories/TrackRepository.kt b/app/src/main/java/com/example/mear/repositories/TrackRepository.kt index 3d5a3be..deb912d 100644 --- a/app/src/main/java/com/example/mear/repositories/TrackRepository.kt +++ b/app/src/main/java/com/example/mear/repositories/TrackRepository.kt @@ -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 = context.database.use { + constructor(context: Context) + { + this.context = context + } + + fun getAll(): List = context!!.database.use { val tracks = mutableListOf() 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{ override fun parseRow(columns: Map): 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{ override fun parseRow(columns: Map): 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 { + override fun parseRow(columns: Map): Int { + return columns.getValue("TotalSongs").toString().toInt() + } + }) + } + return 0 + } } \ No newline at end of file diff --git a/app/src/main/java/com/example/mear/extensions.kt b/app/src/main/java/extensions.kt similarity index 71% rename from app/src/main/java/com/example/mear/extensions.kt rename to app/src/main/java/extensions.kt index 92f858d..b62d332 100644 --- a/app/src/main/java/com/example/mear/extensions.kt +++ b/app/src/main/java/extensions.kt @@ -5,4 +5,4 @@ import android.content.Context import com.example.mear.management.DatabaseManager val Context.database: DatabaseManager - get() = DatabaseManager.getInstance(applicationContext) + get() = DatabaseManager.getInstance(applicationContext) \ No newline at end of file