This commit is contained in:
parent
01b883ed27
commit
cf4374d34b
14 changed files with 648 additions and 127 deletions
|
@ -26,6 +26,8 @@
|
||||||
<entry key="../../../../../layout/compose-model-1637654654111.xml" value="0.33" />
|
<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-1637655458616.xml" value="0.45740740740740743" />
|
||||||
<entry key="../../../../../layout/compose-model-1637656156044.xml" value="0.25" />
|
<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>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -49,21 +49,22 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:1.6.0'
|
implementation 'androidx.core:core-ktx:1.7.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
implementation 'androidx.appcompat:appcompat:1.4.0'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
implementation "androidx.compose.ui:ui:$compose_version"
|
implementation "androidx.compose.ui:ui:$compose_version"
|
||||||
implementation "androidx.compose.material:material:$compose_version"
|
implementation "androidx.compose.material:material:$compose_version"
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
|
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
|
||||||
implementation "androidx.compose.material:material-icons-extended:$compose_version"
|
implementation "androidx.compose.material:material-icons-extended:$compose_version"
|
||||||
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0'
|
||||||
implementation 'androidx.activity:activity-compose:1.3.1'
|
implementation 'androidx.activity:activity-compose:1.4.0'
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0'
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0'
|
||||||
implementation 'su.litvak.chromecast:api-v2:0.11.3'
|
implementation 'su.litvak.chromecast:api-v2:0.11.3'
|
||||||
implementation 'com.github.yausername.youtubedl-android:library:0.12.+'
|
implementation 'com.github.yausername.youtubedl-android:library:0.12.+'
|
||||||
implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.12.+'
|
implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.12.+'
|
||||||
|
implementation "androidx.media:media:1.4.3"
|
||||||
testImplementation 'junit:junit:4.+'
|
testImplementation 'junit:junit:4.+'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/ic_caster_logo"
|
android:icon="@drawable/ic_caster_logo"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -27,7 +29,12 @@
|
||||||
android:name=".ChromecastManagerActivity"
|
android:name=".ChromecastManagerActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/title_activity_chromecast_manager"
|
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
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
@ -39,6 +46,7 @@
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
<service android:name=".services.ChromecastManagerService"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
21
app/src/main/java/eu/toldi/balazs/caster/App.kt
Normal file
21
app/src/main/java/eu/toldi/balazs/caster/App.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,17 @@
|
||||||
package eu.toldi.balazs.caster
|
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 {
|
object ChromeCastHelper {
|
||||||
|
|
||||||
const val APP_BACKDROP = "E8C28D3C"
|
const val APP_BACKDROP = "E8C28D3C"
|
||||||
|
@ -13,4 +25,125 @@ object ChromeCastHelper {
|
||||||
const val APP_BUBBLEUPNP = "3927FA74"
|
const val APP_BUBBLEUPNP = "3927FA74"
|
||||||
const val APP_BBCSOUNDS = "03977A48"
|
const val APP_BBCSOUNDS = "03977A48"
|
||||||
const val APP_BBCIPLAYER = "5E81F6DB"
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
package eu.toldi.balazs.caster
|
package eu.toldi.balazs.caster
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
@ -14,6 +17,8 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import eu.toldi.balazs.caster.ui.theme.CasterTheme
|
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.YoutubeDLException
|
||||||
import com.yausername.youtubedl_android.mapper.VideoInfo
|
import com.yausername.youtubedl_android.mapper.VideoInfo
|
||||||
import eu.toldi.balazs.caster.model.ChromecastManageViewmodel
|
import eu.toldi.balazs.caster.model.ChromecastManageViewmodel
|
||||||
|
import eu.toldi.balazs.caster.services.ChromecastManagerService
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import su.litvak.chromecast.api.v2.Media
|
import su.litvak.chromecast.api.v2.Media
|
||||||
import su.litvak.chromecast.api.v2.MediaStatus
|
import su.litvak.chromecast.api.v2.MediaStatus
|
||||||
|
@ -37,69 +43,31 @@ import su.litvak.chromecast.api.v2.MediaStatus
|
||||||
class ChromecastManagerActivity : ComponentActivity() {
|
class ChromecastManagerActivity : ComponentActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
var chromeCast_: ChromeCast? = null
|
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 chromeCast: ChromeCast
|
||||||
|
private lateinit var viewModel : ChromecastManageViewmodel
|
||||||
|
@RequiresApi(Build.VERSION_CODES.O)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
if (chromeCast_ == null)
|
if (chromeCast_ == null)
|
||||||
finish()
|
finish()
|
||||||
chromeCast = chromeCast_ as ChromeCast
|
chromeCast = chromeCast_ as ChromeCast
|
||||||
|
ChromeCastHelper.chromeCast = chromeCast
|
||||||
try {
|
try {
|
||||||
YoutubeDL.getInstance().init(application)
|
YoutubeDL.getInstance().init(application)
|
||||||
} catch (e: YoutubeDLException) {
|
} catch (e: YoutubeDLException) {
|
||||||
Log.e("Caster", "failed to initialize youtubedl-android", e)
|
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 {
|
setContent {
|
||||||
CasterTheme {
|
CasterTheme {
|
||||||
val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
|
viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
|
||||||
viewModel.chromeCast = chromeCast
|
viewModel.chromeCast = chromeCast
|
||||||
// A surface container using the 'background' color from the theme
|
// A surface container using the 'background' color from the theme
|
||||||
Surface(color = MaterialTheme.colors.background) {
|
Surface(color = MaterialTheme.colors.background) {
|
||||||
|
@ -123,38 +91,53 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
label = { Text("Cast URL") },
|
label = { Text("Cast URL") },
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
)
|
)
|
||||||
var castEnabled by remember { mutableStateOf(true)}
|
var castEnabled by remember { mutableStateOf(true) }
|
||||||
Button(
|
Button(
|
||||||
enabled = castEnabled,
|
enabled = castEnabled,
|
||||||
onClick = {
|
onClick = {
|
||||||
castEnabled = false
|
castEnabled = false
|
||||||
lifecycleScope.launch {
|
viewModel.castLink(text) {
|
||||||
castLink(chromeCast, text) {
|
castEnabled = true
|
||||||
castEnabled = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = "Cast")
|
Text(text = "Cast")
|
||||||
}
|
}
|
||||||
if(castEnabled.not()){
|
if (castEnabled.not()) {
|
||||||
CircularProgressIndicator()
|
CircularProgressIndicator()
|
||||||
}
|
}
|
||||||
playBackControl(viewmodel = viewModel)
|
playBackControl()
|
||||||
val mediaStatus = viewModel.mediaStatus.value
|
val mediaStatus = viewModel.mediaStatus.value
|
||||||
|
|
||||||
if(mediaStatus != null) {
|
if (mediaStatus != null) {
|
||||||
val nowPlaying = mediaStatus.media.metadata[Media.METADATA_TITLE]
|
val nowPlaying = mediaStatus.media.metadata[Media.METADATA_TITLE]
|
||||||
Text(text = "Now playing: $nowPlaying")
|
Text(text = "Now playing: $nowPlaying")
|
||||||
val progress = mediaStatus.currentTime/ mediaStatus.media.duration
|
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()))
|
Row(
|
||||||
Text(text = String.format("%02d:%02d",((mediaStatus.media.duration % 3600) / 60).toInt(),(mediaStatus.media.duration % 60).toInt()))
|
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 = {
|
Slider(value = sliderPosition, onValueChange = {
|
||||||
viewModel.seek(it.toDouble())
|
sliderPosition = it
|
||||||
|
}, onValueChangeFinished = {
|
||||||
|
viewModel.seek(sliderPosition.toDouble())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -188,18 +171,15 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun playBackControl(viewmodel: ChromecastManageViewmodel){
|
fun playBackControl() {
|
||||||
val mediaStatus = viewmodel.mediaStatus.value
|
val mediaStatus = viewModel.mediaStatus.value
|
||||||
Row {
|
Row {
|
||||||
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if(mediaStatus != null) {
|
if (mediaStatus != null) {
|
||||||
lifecycleScope.launch(IO) {
|
viewModel.seek(chromeCast.mediaStatus.currentTime - 10)
|
||||||
chromeCast.seek(chromeCast.mediaStatus.currentTime - 10)
|
}
|
||||||
viewmodel.fetchMediaStatus()
|
}, enabled = mediaStatus != null) {
|
||||||
}
|
|
||||||
}
|
|
||||||
},enabled = mediaStatus != null) {
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.FastRewind,
|
Icons.Filled.FastRewind,
|
||||||
contentDescription = "FastRewind"
|
contentDescription = "FastRewind"
|
||||||
|
@ -212,18 +192,18 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
playBackState = chromeCast.mediaStatus.playerState
|
playBackState = chromeCast.mediaStatus.playerState
|
||||||
}*/
|
}*/
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
lifecycleScope.launch(IO) {
|
|
||||||
if(mediaStatus != null) {
|
if (mediaStatus != null) {
|
||||||
if (mediaStatus.playerState == MediaStatus.PlayerState.PLAYING) {
|
if (mediaStatus.playerState == MediaStatus.PlayerState.PLAYING) {
|
||||||
chromeCast.pause()
|
viewModel.pause()
|
||||||
}
|
|
||||||
if (mediaStatus.playerState == MediaStatus.PlayerState.PAUSED) {
|
|
||||||
chromeCast.play()
|
|
||||||
}
|
|
||||||
viewmodel.fetchMediaStatus()
|
|
||||||
}
|
}
|
||||||
|
if (mediaStatus.playerState == MediaStatus.PlayerState.PAUSED) {
|
||||||
|
viewModel.play()
|
||||||
|
}
|
||||||
|
viewModel.fetchMediaStatus()
|
||||||
}
|
}
|
||||||
},enabled = mediaStatus != null) {
|
|
||||||
|
}, enabled = mediaStatus != null) {
|
||||||
when {
|
when {
|
||||||
mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.PAUSED -> Icon(
|
mediaStatus == null || mediaStatus.playerState == MediaStatus.PlayerState.PAUSED -> Icon(
|
||||||
Icons.Filled.PlayArrow,
|
Icons.Filled.PlayArrow,
|
||||||
|
@ -242,13 +222,8 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
|
|
||||||
}
|
}
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
lifecycleScope.launch(IO) {
|
viewModel.stopApp()
|
||||||
if(chromeCast.runningApp != null) {
|
}, enabled = mediaStatus != null) {
|
||||||
chromeCast.stopApp()
|
|
||||||
viewmodel.fetchMediaStatus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},enabled = mediaStatus != null) {
|
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.Stop,
|
Icons.Filled.Stop,
|
||||||
contentDescription = "Stop"
|
contentDescription = "Stop"
|
||||||
|
@ -256,13 +231,10 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
lifecycleScope.launch(IO) {
|
if (chromeCast.mediaStatus != null) {
|
||||||
if(chromeCast.mediaStatus != null) {
|
viewModel.seek(chromeCast.mediaStatus.currentTime + 10)
|
||||||
chromeCast.seek(chromeCast.mediaStatus.currentTime + 10)
|
|
||||||
viewmodel.fetchMediaStatus()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},enabled = mediaStatus != null) {
|
}, enabled = mediaStatus != null) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Filled.FastForward,
|
Icons.Filled.FastForward,
|
||||||
contentDescription = "FastForward"
|
contentDescription = "FastForward"
|
||||||
|
@ -302,26 +274,12 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.padding(vertical = 4.dp)
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
) {
|
) {
|
||||||
Text(text = "Cast")
|
Text(text = "Cast")
|
||||||
}
|
}
|
||||||
// val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
|
// val viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
|
||||||
//playBackControl(viewModel)
|
//playBackControl(viewModel)
|
||||||
Text(text = "Now playing: Some video")
|
Text(text = "Now playing: Some video")
|
||||||
var sliderPosition by remember { mutableStateOf(0f) }
|
var sliderPosition by remember { mutableStateOf(0f) }
|
||||||
|
@ -332,16 +290,14 @@ class ChromecastManagerActivity : ComponentActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
if(keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
GlobalScope.launch(IO) {
|
viewModel.setVolume(chromeCast.status.volume.level + 0.05f)
|
||||||
chromeCast.setVolume(chromeCast.status.volume.level + 0.05f)
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
GlobalScope.launch(IO) {
|
|
||||||
chromeCast.setVolume(chromeCast.status.volume.level - 0.05f)
|
viewModel.setVolume(chromeCast.status.volume.level - 0.05f)
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return super.onKeyDown(keyCode, event)
|
return super.onKeyDown(keyCode, event)
|
||||||
|
|
|
@ -107,7 +107,8 @@ class ShareRecieverActivity : ComponentActivity() {
|
||||||
onClick = {
|
onClick = {
|
||||||
onEnableChanged(false)
|
onEnableChanged(false)
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
ChromecastManagerActivity.castLink(chromeCast, link) {
|
ChromeCastHelper.chromeCast = chromeCast
|
||||||
|
ChromeCastHelper.castLink(link) {
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
10
app/src/main/res/drawable/forward.xml
Normal file
10
app/src/main/res/drawable/forward.xml
Normal 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>
|
10
app/src/main/res/drawable/pause.xml
Normal file
10
app/src/main/res/drawable/pause.xml
Normal 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>
|
10
app/src/main/res/drawable/play.xml
Normal file
10
app/src/main/res/drawable/play.xml
Normal 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>
|
10
app/src/main/res/drawable/rewind.xml
Normal file
10
app/src/main/res/drawable/rewind.xml
Normal 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>
|
|
@ -24,7 +24,7 @@ ext.getVersionCode = { ->
|
||||||
try {
|
try {
|
||||||
def stdout = new ByteArrayOutputStream()
|
def stdout = new ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine 'git', 'rev-list', '--first-parent', '--count', 'origin/main'
|
commandLine 'git', 'rev-list', '--first-parent', '--count', '--all'
|
||||||
standardOutput = stdout
|
standardOutput = stdout
|
||||||
}
|
}
|
||||||
return Integer.parseInt(stdout.toString().trim())
|
return Integer.parseInt(stdout.toString().trim())
|
||||||
|
|
Loading…
Reference in a new issue