Skip to content

Commit

Permalink
Merge pull request #26 from NikolayRyabcev/dev
Browse files Browse the repository at this point in the history
Спринт 25 ДЗ
  • Loading branch information
NikolayRyabcev authored Apr 27, 2024
2 parents ee434cc + 44220fe commit 1d3c0d7
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 113 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ android {
}

dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.core:core-ktx:1.13.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>



<application
Expand All @@ -17,9 +21,11 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:targetApi="31">
tools:targetApi="31"
tools:ignore="ForegroundServicePermission">

<activity android:name=".ui.root.RootActivity"
<activity
android:name=".ui.root.RootActivity"
android:exported="true"
android:theme="@style/Theme.PlaylistMaker"
android:windowSoftInputMode="adjustNothing">
Expand All @@ -38,7 +44,8 @@
<service
android:name=".services.MusicService"
android:exported="false"
android:foregroundServiceType="mediaPlayback" />
android:foregroundServiceType="mediaPlayback">
</service>

</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,45 @@ import java.text.SimpleDateFormat

class PlayerRepositoryImpl : PlayerRepository {
private val mediaPlayer = MediaPlayer()
private var playerState = PlayerState.STATE_DEFAULT
private var playerState :PlayerState = PlayerState.Default

private lateinit var listener: PlayerStateListener
private var playerJob: Job? = null

override fun preparePlayer(url: String, listener: PlayerStateListener) {
this.listener = listener
if (playerState != PlayerState.STATE_DEFAULT) return
if (playerState != PlayerState.Default) return
listener.onStateChanged(playerState)
mediaPlayer.reset()
mediaPlayer.setDataSource(url)
mediaPlayer.prepareAsync()
mediaPlayer.setOnPreparedListener {
playerState = PlayerState.STATE_PREPARED
playerState = PlayerState.Prepared
listener.onStateChanged(playerState)
playerJob?.start()
}
mediaPlayer.setOnCompletionListener {
playerState = PlayerState.STATE_PREPARED
playerState = PlayerState.Prepared
listener.onStateChanged(playerState)
}
}


override fun play() {
mediaPlayer.start()
playerState = PlayerState.STATE_PLAYING
playerState = PlayerState.Playing("00:00")
listener.onStateChanged(playerState)
}

override fun pause() {
mediaPlayer.pause()
playerState = PlayerState.STATE_PAUSED
playerState = PlayerState.Paused("")
listener.onStateChanged(playerState)
}

override fun destroy() {
mediaPlayer.release()
playerState = PlayerState.STATE_DEFAULT
playerState = PlayerState.Default
listener.onStateChanged(playerState)
playerJob?.cancel()
}
Expand All @@ -60,7 +60,7 @@ class PlayerRepositoryImpl : PlayerRepository {
override fun timing(): Flow<String> = flow {
val sdf = SimpleDateFormat("mm:ss")
while (true) {
if ((playerState == PlayerState.STATE_PLAYING) or (playerState == PlayerState.STATE_PAUSED)) {
if ((playerState is PlayerState.Playing) or (playerState is PlayerState.Paused)) {
emit(sdf.format(mediaPlayer.currentPosition))
} else {
emit("00:00")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,5 @@ val playerModule = module {
PlayerInteractorImpl(get())
}

viewModel { PlayerViewModel(get(), get(), get()) }
viewModel { PlayerViewModel(get(), get()) }
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.example.playlistmaker.domain.player

enum class PlayerState {
STATE_DEFAULT,
STATE_PREPARED,
STATE_PLAYING,
STATE_PAUSED
}
sealed class PlayerState {
object Default : PlayerState()
object Prepared : PlayerState()
data class Playing (val position: String) : PlayerState()
data class Paused (val position: String): PlayerState()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.playlistmaker.services

import com.example.playlistmaker.domain.player.PlayerState
import kotlinx.coroutines.flow.StateFlow

interface AudioPlayerControl {
fun getPlayerState(): StateFlow<PlayerState>
fun startPlayer()
fun pausePlayer()
fun provideNotificator()
fun stopNotification()
}
166 changes: 163 additions & 3 deletions app/src/main/java/com/example/playlistmaker/services/MusicService.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,178 @@
package com.example.playlistmaker.services

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
import android.media.MediaPlayer
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat
import com.example.playlistmaker.R
import com.example.playlistmaker.domain.player.PlayerState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.Locale

class MusicService : Service() {
private val binder=MusicServiceBinder()
class MusicService : Service(), AudioPlayerControl {

private val binder = MusicServiceBinder()

private val _playerState = MutableStateFlow<PlayerState>(PlayerState.Default)
private val playerServiceState = _playerState.asStateFlow()

private var songUrl = ""
private var trackInfo = ""

private var mediaPlayer: MediaPlayer? = null

private var timerJob: Job? = null

private fun startTimer() {
timerJob = CoroutineScope(Dispatchers.Default).launch {
while (mediaPlayer?.isPlaying == true) {
//if (getCurrentPlayerPosition()== "00:29") return@launch
_playerState.value = PlayerState.Playing(getCurrentPlayerPosition())
delay(200L)
Log.d("плеер ", "startTimer")
}
}
}

override fun onCreate() {
super.onCreate()
mediaPlayer = MediaPlayer()
}

private fun getForegroundServiceTypeConstant(): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
} else {
0
}
}

override fun onBind(intent: Intent?): IBinder? {
songUrl = intent?.getStringExtra("song_url") ?: ""
val artist = intent?.getStringExtra("songArtist") ?: ""
val track = intent?.getStringExtra("songName") ?: ""
trackInfo = "$artist - $track"
initMediaPlayer()
return binder
}

//уведомления
override fun provideNotificator() {
createNotificationChannel()
ServiceCompat.startForeground(
this,
SERVICE_NOTIFICATION_ID,
createServiceNotification(),
getForegroundServiceTypeConstant()
)
}

override fun stopNotification() {
stopForeground(true)
}

private fun createNotificationChannel() {
Log.d("плеер", "createNotificationChannel")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
Log.d("плеер", "createNotificationChannelIfBranch")
return
}

val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
"Music service",
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = "Service for playing music"

val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}

private fun createServiceNotification(): Notification {
return NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setContentTitle("Playlist Maker")
.setContentText(trackInfo)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.build()
}

override fun onUnbind(intent: Intent?): Boolean {
releasePlayer()
return super.onUnbind(intent)
}

private fun initMediaPlayer() {
if (songUrl.isEmpty()) return
mediaPlayer?.setDataSource(songUrl)
mediaPlayer?.setOnPreparedListener {
_playerState.value = PlayerState.Prepared
}
mediaPlayer?.setOnCompletionListener {
_playerState.value = PlayerState.Prepared
}
mediaPlayer?.prepareAsync()
}

override fun startPlayer() {
mediaPlayer?.start()
_playerState.value = PlayerState.Playing(getCurrentPlayerPosition())
startTimer()
}

override fun pausePlayer() {
mediaPlayer?.pause()
timerJob?.cancel()
_playerState.value = PlayerState.Paused(getCurrentPlayerPosition())
}

override fun getPlayerState(): StateFlow<PlayerState> {
return playerServiceState
}

private fun releasePlayer() {
timerJob?.cancel()
mediaPlayer?.stop()
_playerState.value = PlayerState.Default
mediaPlayer?.setOnPreparedListener(null)
mediaPlayer?.setOnCompletionListener(null)
mediaPlayer?.release()
mediaPlayer = null
}

private fun getCurrentPlayerPosition(): String {
return SimpleDateFormat("mm:ss", Locale.getDefault()).format(mediaPlayer?.currentPosition)
?: "00:00"
}

inner class MusicServiceBinder : Binder() {
fun getMusicService(): MusicService = this@MusicService
}
}

private companion object {
const val LOG_TAG = "MusicService"
const val NOTIFICATION_CHANNEL_ID = "music_service_channel"
const val SERVICE_NOTIFICATION_ID = 100
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.example.playlistmaker.ui.player.buttonView

interface CustomViewClickListener {
fun onViewClicked()
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,12 @@ class PlaybackButtonView @JvmOverloads constructor(
private var imageRect = RectF(0f, 0f, 0f, 0f)
private var isPlaying = false
var onTouchListener: (() -> Unit)? = null
var clickListener: CustomViewClickListener? = null

init {
setOnClickListener {
clickListener?.onViewClicked()
}
context.theme.obtainStyledAttributes(
attrs,
R.styleable.PlaybackButtonView,
Expand Down Expand Up @@ -65,6 +69,7 @@ class PlaybackButtonView @JvmOverloads constructor(
}

override fun performClick(): Boolean {

return super.performClick()
}

Expand All @@ -77,6 +82,7 @@ class PlaybackButtonView @JvmOverloads constructor(

MotionEvent.ACTION_UP -> {
playerSwitch()
clickListener?.onViewClicked()
invalidate()
return true
}
Expand All @@ -101,6 +107,5 @@ class PlaybackButtonView @JvmOverloads constructor(
imageToShow = imagePlay
isPlaying = false
invalidate()
Log.d("КастомВью", "onStopped")
}
}
Loading

0 comments on commit 1d3c0d7

Please sign in to comment.