Major refactoring
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Balazs Toldi 2021-11-23 16:28:33 +01:00
parent 01b883ed27
commit cf4374d34b
Signed by: Bazsalanszky
GPG key ID: 933820884952BE27
14 changed files with 648 additions and 127 deletions

View file

@ -26,6 +26,8 @@
<entry key="../../../../../layout/compose-model-1637654654111.xml" value="0.33" />
<entry key="../../../../../layout/compose-model-1637655458616.xml" value="0.45740740740740743" />
<entry key="../../../../../layout/compose-model-1637656156044.xml" value="0.25" />
<entry key="../../../../../layout/compose-model-1637673325505.xml" value="0.11570945945945946" />
<entry key="../../../../../layout/compose-model-1637674242617.xml" value="0.49537037037037035" />
</map>
</option>
</component>

View file

@ -49,21 +49,22 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.3.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
implementation 'androidx.activity:activity-compose:1.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0'
implementation 'su.litvak.chromecast:api-v2:0.11.3'
implementation 'com.github.yausername.youtubedl-android:library:0.12.+'
implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.12.+'
implementation "androidx.media:media:1.4.3"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

View file

@ -4,8 +4,10 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_caster_logo"
android:label="@string/app_name"
@ -27,7 +29,12 @@
android:name=".ChromecastManagerActivity"
android:exported="true"
android:label="@string/title_activity_chromecast_manager"
android:theme="@style/Theme.Caster.NoActionBar" />
android:theme="@style/Theme.Caster.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true"
@ -39,6 +46,7 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".services.ChromecastManagerService"/>
</application>
</manifest>

View file

@ -0,0 +1,21 @@
package eu.toldi.balazs.caster
import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.os.Build
class App : Application() {
companion object {
const val CHANNEL_ID = "casterServiceNotificationChannel"
}
override fun onCreate() {
super.onCreate()
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(CHANNEL_ID,"Caster Service Channel",NotificationManager.IMPORTANCE_LOW)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
}

View file

@ -1,5 +1,17 @@
package eu.toldi.balazs.caster
import android.util.Log
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLException
import com.yausername.youtubedl_android.YoutubeDLRequest
import com.yausername.youtubedl_android.mapper.VideoInfo
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import su.litvak.chromecast.api.v2.Application
import su.litvak.chromecast.api.v2.ChromeCast
import su.litvak.chromecast.api.v2.MediaStatus
object ChromeCastHelper {
const val APP_BACKDROP = "E8C28D3C"
@ -13,4 +25,125 @@ object ChromeCastHelper {
const val APP_BUBBLEUPNP = "3927FA74"
const val APP_BBCSOUNDS = "03977A48"
const val APP_BBCIPLAYER = "5E81F6DB"
lateinit var chromeCast: ChromeCast
fun getApplicationDisplayName(id: String): String {
return when (id) {
APP_MEDIA_RECEIVER -> "Media Player"
APP_YOUTUBE -> "Youtube"
APP_PLEX -> "Plex"
APP_BACKDROP -> "Idle"
APP_DASHCAST -> "Dashcast"
APP_HOME_ASSISTANT -> "Home Assistant"
APP_SUPLA -> "Supla"
APP_YLEAREENA -> "Yleareena"
APP_BUBBLEUPNP -> "BubbleUPNP"
APP_BBCSOUNDS -> "BBC Sounds"
APP_BBCIPLAYER -> "BBC IPlayer"
else -> "Unknown App $id"
}
}
suspend fun fetchMediaStatus(): MediaStatus? {
try {
var mediaStatus : MediaStatus? = null
withContext(IO) {
val status = chromeCast.status
if (status.runningApp != null) {
mediaStatus = chromeCast.mediaStatus
}
}
return mediaStatus
} catch (e: Exception) {
Log.e(null, e.stackTraceToString())
}
return null
}
suspend fun seek(d: Double) {
val mediaStatus = fetchMediaStatus()
if (mediaStatus != null) {
try {
withContext(IO) {
chromeCast.seek(d * mediaStatus.media.duration)
}
} catch (e: Exception) {
Log.e(null, e.stackTraceToString())
}
}
}
suspend fun stopApp() {
withContext(IO) {
if (chromeCast.runningApp != null) {
chromeCast.stopApp()
}
}
}
suspend fun play() {
withContext(IO) {
chromeCast.play()
}
}
suspend fun pause(){
withContext(IO) {
chromeCast.pause()
}
}
suspend fun setVolume(f: Float) {
withContext(IO) {
chromeCast.setVolume(f)
}
}
suspend fun castLink(link : String, callBack : () -> Unit = {}) : Boolean{
var exitStatus = true
if(link.startsWith("https://").not() && link.startsWith("http://").not()) {
callBack.invoke()
return false
}
withContext(IO){
val request =
YoutubeDLRequest(link)
request.addOption("-f", "best")
val _streamInfo = try {
YoutubeDL.getInstance().getInfo(request)
} catch (e: YoutubeDLException) {
null
}
if (_streamInfo == null) {
exitStatus = false
callBack.invoke()
} else {
val streamInfo = _streamInfo as VideoInfo
val status = chromeCast.status
if (chromeCast.isAppAvailable(ChromeCastHelper.APP_MEDIA_RECEIVER) && !status.isAppRunning(
ChromeCastHelper.APP_MEDIA_RECEIVER
)
) {
val app: Application = chromeCast.launchApp(ChromeCastHelper.APP_MEDIA_RECEIVER)
}
while (!chromeCast.status.isAppRunning(ChromeCastHelper.APP_MEDIA_RECEIVER)) {
delay(100)
}
chromeCast.load(
streamInfo.title,
streamInfo.thumbnail,
streamInfo.url,
null
)
callBack.invoke()
}
}
return exitStatus
}
}

View file

@ -1,10 +1,13 @@
package eu.toldi.balazs.caster
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
@ -14,6 +17,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import eu.toldi.balazs.caster.ui.theme.CasterTheme
@ -29,6 +34,7 @@ import com.yausername.youtubedl_android.YoutubeDLRequest
import com.yausername.youtubedl_android.YoutubeDLException
import com.yausername.youtubedl_android.mapper.VideoInfo
import eu.toldi.balazs.caster.model.ChromecastManageViewmodel
import eu.toldi.balazs.caster.services.ChromecastManagerService
import kotlinx.coroutines.withContext
import su.litvak.chromecast.api.v2.Media
import su.litvak.chromecast.api.v2.MediaStatus
@ -37,69 +43,31 @@ import su.litvak.chromecast.api.v2.MediaStatus
class ChromecastManagerActivity : ComponentActivity() {
companion object {
var chromeCast_: ChromeCast? = null
suspend fun castLink(chromeCast: ChromeCast,link : String, callBack : () -> Unit = {}) : Boolean{
var exitStatus = true
if(link.startsWith("https://").not() && link.startsWith("http://").not()) {
callBack.invoke()
return false
}
withContext(IO){
val request =
YoutubeDLRequest(link)
request.addOption("-f", "best")
val _streamInfo = try {
YoutubeDL.getInstance().getInfo(request)
} catch (e: YoutubeDLException) {
null
}
if (_streamInfo == null) {
exitStatus = false
callBack.invoke()
} else {
val streamInfo = _streamInfo as VideoInfo
val status = chromeCast.status
if (chromeCast.isAppAvailable(ChromeCastHelper.APP_MEDIA_RECEIVER) && !status.isAppRunning(
ChromeCastHelper.APP_MEDIA_RECEIVER
)
) {
val app: Application = chromeCast.launchApp(ChromeCastHelper.APP_MEDIA_RECEIVER)
}
while (!chromeCast.status.isAppRunning(ChromeCastHelper.APP_MEDIA_RECEIVER)) {
delay(100)
}
chromeCast.load(
streamInfo.title,
streamInfo.thumbnail,
streamInfo.url,
null
)
callBack.invoke()
}
}
return exitStatus
}
}
private lateinit var chromeCast: ChromeCast
private lateinit var viewModel : ChromecastManageViewmodel
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (chromeCast_ == null)
finish()
chromeCast = chromeCast_ as ChromeCast
ChromeCastHelper.chromeCast = chromeCast
try {
YoutubeDL.getInstance().init(application)
} catch (e: YoutubeDLException) {
Log.e("Caster", "failed to initialize youtubedl-android", e)
}
val serviceIntent = Intent(this, ChromecastManagerService::class.java).also {
it.action = ChromecastManagerService.ACTION_INIT
it.putExtra("CHROMECAST_ADDRESS", chromeCast.address)
it.putExtra("CHROMECAST_NAME", chromeCast.title)
ContextCompat.startForegroundService(this, it)
}
setContent {
CasterTheme {
val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
viewModel.chromeCast = chromeCast
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
@ -123,38 +91,53 @@ class ChromecastManagerActivity : ComponentActivity() {
label = { Text("Cast URL") },
modifier = Modifier.padding(vertical = 4.dp)
)
var castEnabled by remember { mutableStateOf(true)}
var castEnabled by remember { mutableStateOf(true) }
Button(
enabled = castEnabled,
onClick = {
castEnabled = false
lifecycleScope.launch {
castLink(chromeCast, text) {
castEnabled = true
}
viewModel.castLink(text) {
castEnabled = true
}
},
modifier = Modifier.padding(vertical = 4.dp)
) {
Text(text = "Cast")
}
if(castEnabled.not()){
if (castEnabled.not()) {
CircularProgressIndicator()
}
playBackControl(viewmodel = viewModel)
playBackControl()
val mediaStatus = viewModel.mediaStatus.value
if(mediaStatus != null) {
if (mediaStatus != null) {
val nowPlaying = mediaStatus.media.metadata[Media.METADATA_TITLE]
Text(text = "Now playing: $nowPlaying")
val progress = mediaStatus.currentTime/ mediaStatus.media.duration
Row(Modifier.fillMaxWidth(),horizontalArrangement = Arrangement.SpaceBetween) {
Text(text = String.format("%02d:%02d",((mediaStatus.currentTime % 3600) / 60).toInt(), (mediaStatus.currentTime % 60).toInt()))
Text(text = String.format("%02d:%02d",((mediaStatus.media.duration % 3600) / 60).toInt(),(mediaStatus.media.duration % 60).toInt()))
var sliderPosition by remember { mutableStateOf((mediaStatus.currentTime / mediaStatus.media.duration).toFloat()) }
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text(
text = String.format(
"%02d:%02d",
((mediaStatus.currentTime % 3600) / 60).toInt(),
(mediaStatus.currentTime % 60).toInt()
)
)
Text(
text = String.format(
"%02d:%02d",
((mediaStatus.media.duration % 3600) / 60).toInt(),
(mediaStatus.media.duration % 60).toInt()
)
)
}
Slider(value = progress.toFloat(), onValueChange = {
viewModel.seek(it.toDouble())
Slider(value = sliderPosition, onValueChange = {
sliderPosition = it
}, onValueChangeFinished = {
viewModel.seek(sliderPosition.toDouble())
})
}
}
@ -188,18 +171,15 @@ class ChromecastManagerActivity : ComponentActivity() {
}
@Composable
fun playBackControl(viewmodel: ChromecastManageViewmodel){
val mediaStatus = viewmodel.mediaStatus.value
fun playBackControl() {
val mediaStatus = viewModel.mediaStatus.value
Row {
IconButton(onClick = {
if(mediaStatus != null) {
lifecycleScope.launch(IO) {
chromeCast.seek(chromeCast.mediaStatus.currentTime - 10)
viewmodel.fetchMediaStatus()
}
}
},enabled = mediaStatus != null) {
if (mediaStatus != null) {
viewModel.seek(chromeCast.mediaStatus.currentTime - 10)
}
}, enabled = mediaStatus != null) {
Icon(
Icons.Filled.FastRewind,
contentDescription = "FastRewind"
@ -212,18 +192,18 @@ class ChromecastManagerActivity : ComponentActivity() {
playBackState = chromeCast.mediaStatus.playerState
}*/
IconButton(onClick = {
lifecycleScope.launch(IO) {
if(mediaStatus != null) {
if (mediaStatus.playerState == MediaStatus.PlayerState.PLAYING) {
chromeCast.pause()
}
if (mediaStatus.playerState == MediaStatus.PlayerState.PAUSED) {
chromeCast.play()
}
viewmodel.fetchMediaStatus()
if (mediaStatus != null) {
if (mediaStatus.playerState == MediaStatus.PlayerState.PLAYING) {
viewModel.pause()
}
if (mediaStatus.playerState == MediaStatus.PlayerState.PAUSED) {
viewModel.play()
}
viewModel.fetchMediaStatus()
}
},enabled = mediaStatus != null) {
}, enabled = mediaStatus != null) {
when {
mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.PAUSED -> Icon(
Icons.Filled.PlayArrow,
@ -242,13 +222,8 @@ class ChromecastManagerActivity : ComponentActivity() {
}
IconButton(onClick = {
lifecycleScope.launch(IO) {
if(chromeCast.runningApp != null) {
chromeCast.stopApp()
viewmodel.fetchMediaStatus()
}
}
},enabled = mediaStatus != null) {
viewModel.stopApp()
}, enabled = mediaStatus != null) {
Icon(
Icons.Filled.Stop,
contentDescription = "Stop"
@ -256,13 +231,10 @@ class ChromecastManagerActivity : ComponentActivity() {
}
IconButton(onClick = {
lifecycleScope.launch(IO) {
if(chromeCast.mediaStatus != null) {
chromeCast.seek(chromeCast.mediaStatus.currentTime + 10)
viewmodel.fetchMediaStatus()
}
if (chromeCast.mediaStatus != null) {
viewModel.seek(chromeCast.mediaStatus.currentTime + 10)
}
},enabled = mediaStatus != null) {
}, enabled = mediaStatus != null) {
Icon(
Icons.Filled.FastForward,
contentDescription = "FastForward"
@ -302,26 +274,12 @@ class ChromecastManagerActivity : ComponentActivity() {
modifier = Modifier.padding(vertical = 4.dp)
)
Button(
onClick = {
GlobalScope.launch(IO) {
val status = chromeCast.getStatus()
if (chromeCast.isAppAvailable("CC1AD845") && !status.isAppRunning(
"CC1AD845"
)
) {
val app: Application = chromeCast.launchApp("CC1AD845")
}
while (!status.isAppRunning("CC1AD845")) {
delay(100)
}
chromeCast.load(text)
}
},
onClick = {},
modifier = Modifier.padding(vertical = 4.dp)
) {
Text(text = "Cast")
}
// val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
// val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
//playBackControl(viewModel)
Text(text = "Now playing: Some video")
var sliderPosition by remember { mutableStateOf(0f) }
@ -332,16 +290,14 @@ class ChromecastManagerActivity : ComponentActivity() {
}
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
if(keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
GlobalScope.launch(IO) {
chromeCast.setVolume(chromeCast.status.volume.level + 0.05f)
}
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
viewModel.setVolume(chromeCast.status.volume.level + 0.05f)
return true
}
if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
GlobalScope.launch(IO) {
chromeCast.setVolume(chromeCast.status.volume.level - 0.05f)
}
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
viewModel.setVolume(chromeCast.status.volume.level - 0.05f)
return true
}
return super.onKeyDown(keyCode, event)

View file

@ -107,7 +107,8 @@ class ShareRecieverActivity : ComponentActivity() {
onClick = {
onEnableChanged(false)
lifecycleScope.launch {
ChromecastManagerActivity.castLink(chromeCast, link) {
ChromeCastHelper.chromeCast = chromeCast
ChromeCastHelper.castLink(link) {
finish()
}
}

View file

@ -0,0 +1,83 @@
package eu.toldi.balazs.caster.model
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.toldi.balazs.caster.ChromeCastHelper
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import su.litvak.chromecast.api.v2.ChromeCast
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEvent
import su.litvak.chromecast.api.v2.ChromeCastSpontaneousEventListener
import su.litvak.chromecast.api.v2.MediaStatus
class ChromecastManageViewmodel : ViewModel(), ChromeCastSpontaneousEventListener {
private val _mediaState = mutableStateOf<MediaStatus?>(null)
val mediaStatus: State<MediaStatus?>
get() = _mediaState
lateinit var chromeCast: ChromeCast
init {
viewModelScope.launch(IO) {
while (true) {
fetchMediaStatus()
Thread.sleep(1000)
}
}
}
override fun spontaneousEventReceived(event: ChromeCastSpontaneousEvent?) {
Log.e(null, event?.type.toString())
if (event != null) {
if (event.type == ChromeCastSpontaneousEvent.SpontaneousEventType.MEDIA_STATUS) {
_mediaState.value = event.data as MediaStatus
}
}
}
fun fetchMediaStatus() {
viewModelScope.launch(IO) {
_mediaState.value = ChromeCastHelper.fetchMediaStatus()
}
}
fun seek(d: Double) {
viewModelScope.launch(IO) {
ChromeCastHelper.seek(d)
}
}
fun castLink(link : String,callBack: () -> Unit = {}) {
viewModelScope.launch(IO) {
ChromeCastHelper.castLink(link,callBack)
}
}
fun stopApp() {
viewModelScope.launch(IO) {
ChromeCastHelper.stopApp()
}
}
fun play() {
viewModelScope.launch(IO) {
ChromeCastHelper.play()
}
}
fun pause() {
viewModelScope.launch(IO) {
ChromeCastHelper.pause()
}
}
fun setVolume(f: Float) {
viewModelScope.launch(IO) {
ChromeCastHelper.setVolume(f)
}
}
}

View file

@ -0,0 +1,276 @@
package eu.toldi.balazs.caster.services
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.media.MediaPlayer
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.os.Build
import android.os.IBinder
import android.support.v4.media.session.MediaControllerCompat
import android.support.v4.media.session.MediaSessionCompat
import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import eu.toldi.balazs.caster.App.Companion.CHANNEL_ID
import eu.toldi.balazs.caster.ChromeCastHelper
import eu.toldi.balazs.caster.R
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.IO
import su.litvak.chromecast.api.v2.ChromeCast
import su.litvak.chromecast.api.v2.Media
import su.litvak.chromecast.api.v2.MediaStatus
class ChromecastManagerService : Service() {
companion object {
const val ACTION_INIT = "action_init"
const val ACTION_PLAY = "action_play"
const val ACTION_PAUSE = "action_pause"
const val ACTION_REWIND = "action_rewind"
const val ACTION_FAST_FORWARD = "action_fast_foward"
const val ACTION_NEXT = "action_next"
const val ACTION_PREVIOUS = "action_previous"
const val ACTION_STOP = "action_stop"
}
private val mMediaPlayer = MediaPlayer()
private lateinit var mSession: MediaSessionCompat
private lateinit var mController: MediaControllerCompat
private lateinit var pendingIntent : PendingIntent
private var mediaStatus: MediaStatus? = null
override fun onBind(p0: Intent?): IBinder? = null
private lateinit var chromeCast: ChromeCast
@RequiresApi(Build.VERSION_CODES.M)
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when(intent!!.action) {
ACTION_INIT -> initAction(intent)
ACTION_PLAY -> mController.transportControls.play()
ACTION_PAUSE -> mController.transportControls.pause()
ACTION_FAST_FORWARD -> mController.transportControls.fastForward()
ACTION_REWIND -> mController.transportControls.rewind()
}
return START_REDELIVER_INTENT
}
private fun initAction(intent: Intent?){
initMediaSessions()
val address =
intent?.getStringExtra("CHROMECAST_ADDRESS") ?: throw IllegalArgumentException()
val name = intent?.getStringExtra("CHROMECAST_NAME") ?: "Chromecast"
Log.e(null, address)
chromeCast = ChromeCast(address)
chromeCast.name = name
val notificationIntent = Intent(this, ChromecastManagerService::class.java)
pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0)
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Caster for ${chromeCast.name}@${address}")
.setSmallIcon(R.drawable.ic_caster_logo)
.setContentIntent(pendingIntent).build()
startForeground(1, notification)
GlobalScope.launch(IO) {
while (true) {
try {
ChromeCastHelper.chromeCast = chromeCast
var status = chromeCast.status
Log.d(null, status.applications.toString())
val status_message = if (status.runningApp != null) {
ChromeCastHelper.getApplicationDisplayName(status.runningApp.id)
} else {
"${chromeCast.name} is ready for casting"
}
mediaStatus = ChromeCastHelper.fetchMediaStatus()
buildNotification()
} catch (e: Exception) {
Log.e(null,e.stackTraceToString())
}
Thread.sleep(1000)
}
}
}
private fun generateAction(
icon: Int,
title: String,
intentAction: String
): NotificationCompat.Action {
val intent = Intent(applicationContext, ChromecastManagerService::class.java)
intent.action = intentAction
val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, 0)
return NotificationCompat.Action(icon,title,pendingIntent)
}
private suspend fun buildNotification() {
var status = chromeCast.status
Log.d(null, status.applications.toString())
val status_message = if (status.runningApp != null) {
ChromeCastHelper.getApplicationDisplayName(status.runningApp.id)
} else {
"${chromeCast.name} is ready for xcasting"
}
val notification: Notification
val mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
if (mediaStatus != null) {
notification = if(mediaStatus!!.playerState == MediaStatus.PlayerState.PLAYING) {
NotificationCompat.Builder(baseContext, CHANNEL_ID)
.setContentTitle(status_message)
.setSmallIcon(R.drawable.ic_caster_logo)
.setContentText(mediaStatus!!.media.metadata[Media.METADATA_TITLE].toString())
.addAction(
generateAction(
R.drawable.rewind,
"Rewind",
ACTION_REWIND
)
)
.addAction(
generateAction(
R.drawable.pause,
"Pause",
ACTION_PAUSE
)
)
.addAction(
generateAction(
R.drawable.forward,
"Fast forward",
ACTION_FAST_FORWARD
)
)
// Apply the media style template
.setStyle(androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(1 /* #1: pause button \*/)
.setMediaSession(mSession.sessionToken))
.setContentIntent(pendingIntent).build()
} else {
NotificationCompat.Builder(baseContext, CHANNEL_ID)
.setContentTitle(status_message)
.setSmallIcon(R.drawable.ic_caster_logo)
.setContentText(mediaStatus!!.media.metadata[Media.METADATA_TITLE].toString())
.addAction(
generateAction(
android.R.drawable.ic_media_rew,
"Rewind",
ACTION_REWIND
)
)
.addAction(
generateAction(
android.R.drawable.ic_media_play,
"Pause",
ACTION_PLAY
)
)
.addAction(
generateAction(
android.R.drawable.ic_media_ff,
"Fast forward",
ACTION_FAST_FORWARD
)
)
// Apply the media style template
.setStyle(androidx.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(1 /* #1: pause button \*/)
.setMediaSession(mSession.sessionToken))
.setContentIntent(pendingIntent).build()
}
withContext(Dispatchers.Main) {
mNotificationManager.notify(1, notification)
}
} else {
notification = NotificationCompat.Builder(baseContext, CHANNEL_ID)
.setContentTitle(status_message)
.setSmallIcon(R.drawable.ic_caster_logo)
.setContentIntent(pendingIntent).build()
withContext(Dispatchers.Main) {
mNotificationManager.notify(1, notification)
}
}
}
private fun initMediaSessions() {
mSession = MediaSessionCompat(applicationContext, "simple player session")
mController = MediaControllerCompat(applicationContext, mSession.sessionToken)
mSession.setCallback(object : MediaSessionCompat.Callback() {
override fun onPlay() {
super.onPlay()
Log.e("MediaPlayerService", "onPlay")
GlobalScope.launch(IO) {
if(mediaStatus != null)
ChromeCastHelper.play()
}
}
override fun onPause() {
super.onPause()
Log.e("MediaPlayerService", "onPause")
GlobalScope.launch(IO) {
if(mediaStatus != null)
ChromeCastHelper.pause()
}
}
override fun onFastForward() {
super.onFastForward()
Log.e("MediaPlayerService", "onFastForward")
GlobalScope.launch(IO) {
val mediaStatus = ChromeCastHelper.fetchMediaStatus()
if(mediaStatus != null)
ChromeCastHelper.seek(mediaStatus.currentTime+10)
}
//Manipulate current media here
}
override fun onRewind() {
super.onRewind()
Log.e("MediaPlayerService", "onRewind")
GlobalScope.launch(IO) {
val mediaStatus = ChromeCastHelper.fetchMediaStatus()
if(mediaStatus != null)
ChromeCastHelper.seek(mediaStatus.currentTime-10)
}
}
override fun onStop() {
super.onStop()
Log.e("MediaPlayerService", "onStop")
//Stop media player here
stopForeground(true)
val intent = Intent(applicationContext, ChromecastManagerService::class.java)
stopService(intent)
}
}
)
}
}

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8,5v14l11,-7z"/>
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/>
</vector>

View file

@ -24,7 +24,7 @@ ext.getVersionCode = { ->
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--first-parent', '--count', 'origin/main'
commandLine 'git', 'rev-list', '--first-parent', '--count', '--all'
standardOutput = stdout
}
return Integer.parseInt(stdout.toString().trim())