A commit, that was needed...

This commit is contained in:
Balazs Toldi 2022-09-06 12:01:34 +02:00
parent d90c98a82f
commit 8746f39cde
Signed by: Bazsalanszky
GPG key ID: 6C7D440036F99D58
38 changed files with 1071 additions and 101 deletions

6
.directory Normal file
View file

@ -0,0 +1,6 @@
[Dolphin]
Timestamp=2022,1,4,18,4,55.221
Version=4
[Settings]
HiddenFilesShown=true

View file

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View file

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="deploymentTargetDropDown"> <component name="deploymentTargetDropDown">
<targetSelectedWithDropDown> <runningDeviceTargetSelectedWithDropDown>
<Target> <Target>
<type value="QUICK_BOOT_TARGET" /> <type value="RUNNING_DEVICE_TARGET" />
<deviceKey> <deviceKey>
<Key> <Key>
<type value="VIRTUAL_DEVICE_PATH" /> <type value="SERIAL_NUMBER" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_XL_API_30.avd" /> <value value="adb-a3950b57-giqMub._adb-tls-connect._tcp" />
</Key> </Key>
</deviceKey> </deviceKey>
</Target> </Target>
</targetSelectedWithDropDown> </runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-05T17:39:35.648801Z" /> <timeTargetWasSelectedWithDropDown value="2022-09-06T09:57:52.303689Z" />
<targetsSelectedWithDialog> <targetsSelectedWithDialog>
<Target> <Target>
<type value="QUICK_BOOT_TARGET" /> <type value="QUICK_BOOT_TARGET" />

View file

@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View file

@ -51,6 +51,43 @@
<entry key="../../../../../layout/compose-model-1641222972102.xml" value="0.46296296296296297" /> <entry key="../../../../../layout/compose-model-1641222972102.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1641224248444.xml" value="0.46296296296296297" /> <entry key="../../../../../layout/compose-model-1641224248444.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1641226030069.xml" value="0.46296296296296297" /> <entry key="../../../../../layout/compose-model-1641226030069.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1642254311605.xml" value="0.3015202702702703" />
<entry key="../../../../../layout/compose-model-1642259770760.xml" value="0.22592592592592592" />
<entry key="../../../../../layout/compose-model-1642262177120.xml" value="0.22870370370370371" />
<entry key="../../../../../layout/compose-model-1642342907711.xml" value="0.1" />
<entry key="../../../../../layout/compose-model-1642342992638.xml" value="0.30833333333333335" />
<entry key="../../../../../layout/compose-model-1642342997277.xml" value="0.3015202702702703" />
<entry key="../../../../../layout/compose-model-1642873166547.xml" value="0.1" />
<entry key="../../../../../layout/compose-model-1643299808590.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643300369296.xml" value="1.0" />
<entry key="../../../../../layout/compose-model-1643319277393.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643836978788.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643837012793.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643992156800.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643993639504.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643994288560.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643996450918.xml" value="0.16554054054054054" />
<entry key="../../../../../layout/compose-model-1656272626813.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1657531916246.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1657531926635.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1657532602169.xml" value="1.0" />
<entry key="../../../../../layout/compose-model-1657534975618.xml" value="0.33" />
<entry key="../../../../../layout/compose-model-1657549343896.xml" value="0.1" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/forward.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_launcher_adaptive_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_new_caster_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_new_caster_monochrome_foreground.xml" value="0.101" />
<entry key="app/src/main/res/drawable/pause.xml" value="0.1" />
<entry key="app/src/main/res/drawable/play.xml" value="0.1" />
<entry key="app/src/main/res/drawable/rewind.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_adaptive.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_adaptive_round.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_new_caster_round.xml" value="0.101" />
</map> </map>
</option> </option>
</component> </component>

View file

@ -8,7 +8,7 @@ plugins {
android { android {
compileSdk 31 compileSdk 33
defaultConfig { defaultConfig {
applicationId "eu.toldi.balazs.caster" applicationId "eu.toldi.balazs.caster"
@ -82,6 +82,7 @@ dependencies {
implementation 'com.google.android.material:material:1.6.1' implementation 'com.google.android.material:material:1.6.1'
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.material3:material3:1.0.0-beta01"
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"

View file

@ -10,13 +10,18 @@
<application <application
android:name=".App" android:name=".App"
android:allowBackup="true" android:allowBackup="true"
android:icon="@drawable/ic_caster_logo"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_caster_logo"
android:supportsRtl="true"
android:extractNativeLibs="true" android:extractNativeLibs="true"
android:icon="@mipmap/ic_new_caster"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_new_caster"
android:supportsRtl="true"
android:theme="@style/Theme.Caster"> android:theme="@style/Theme.Caster">
<activity
android:name=".FolderViewActivity"
android:exported="false"
android:label="@string/title_activity_folder_view"
android:theme="@style/Theme.Caster.NoActionBar" />
<activity <activity
android:name=".ShareRecieverActivity" android:name=".ShareRecieverActivity"
android:exported="true" android:exported="true"
@ -24,17 +29,23 @@
android:theme="@style/Theme.Caster.NoActionBar"> android:theme="@style/Theme.Caster.NoActionBar">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" /> <data android:mimeType="image/*" />
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
</activity> </activity>
@ -42,11 +53,10 @@
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> <intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/> <action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
@ -59,7 +69,8 @@
<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"/>
<service android:name=".services.ChromecastManagerService" />
</application> </application>
</manifest> </manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -132,6 +132,7 @@ object ChromeCastHelper {
} }
if (_streamInfo == null) { if (_streamInfo == null) {
exitStatus = false exitStatus = false
Log.e("Caster_ChromecastHelper","Failed to fetch stream!")
callBack.invoke() callBack.invoke()
} else { } else {
try { try {

View file

@ -1,33 +1,43 @@
package eu.toldi.balazs.caster package eu.toldi.balazs.caster
import android.annotation.SuppressLint import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor import android.content.SharedPreferences
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.provider.OpenableColumns
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.webkit.MimeTypeMap
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.filled.*
import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
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.core.content.ContextCompat
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import coil.compose.rememberImagePainter import coil.compose.rememberImagePainter
import com.yausername.ffmpeg.FFmpeg import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL import com.yausername.youtubedl_android.YoutubeDL
@ -36,25 +46,111 @@ import eu.toldi.balazs.caster.helpers.FileCacheHelper
import eu.toldi.balazs.caster.model.ChromecastManageViewmodel import eu.toldi.balazs.caster.model.ChromecastManageViewmodel
import eu.toldi.balazs.caster.services.ChromecastManagerService import eu.toldi.balazs.caster.services.ChromecastManagerService
import eu.toldi.balazs.caster.ui.theme.CasterTheme import eu.toldi.balazs.caster.ui.theme.CasterTheme
import eu.toldi.balazs.caster.ui.theme.getColorScheme
import su.litvak.chromecast.api.v2.ChromeCast import su.litvak.chromecast.api.v2.ChromeCast
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
import java.io.BufferedInputStream import java.io.File
import java.io.BufferedOutputStream import java.util.regex.Matcher
import java.io.FileOutputStream import java.util.regex.Pattern
data class BottomNavItem(
val label: String,
val icon: ImageVector,
val route: String,
)
object Constants {
val BottomNavItems = listOf(
BottomNavItem(
label = "Home",
icon = Icons.Filled.Home,
route = "home"
),
BottomNavItem(
label = "Gallery",
icon = Icons.Filled.Image,
route = "gallery"
),
BottomNavItem(
label = "Settings",
icon = Icons.Filled.Settings,
route = "settings"
)
)
}
class ChromecastManagerActivity : ComponentActivity() { class ChromecastManagerActivity : ComponentActivity() {
companion object { companion object {
var chromeCast_: ChromeCast? = null var chromeCast_: ChromeCast? = null
const val METADATA_THUMB = "thumb" const val METADATA_THUMB = "thumb"
} }
val pattern =
"https?:\\/\\/(?:[0-9A-Z-]+\\.)?(?:youtu\\.be\\/|youtube\\.com\\S*[^\\w\\-\\s])([\\w\\-]{11})(?=[^\\w\\-]|$)(?![?=&+%\\w]*(?:['\"][^<>]*>|<\\/a>))[?=&+%\\w]*"
val compiledPattern: Pattern =
Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)
lateinit var colorScheme: ColorScheme
lateinit var prefs: SharedPreferences
@Composable
fun BottomNavigationBar(navController: NavHostController) {
BottomNavigation(
backgroundColor = colorScheme.secondary,
contentColor = colorScheme.onSecondary
) {
// observe the backstack
val navBackStackEntry by navController.currentBackStackEntryAsState()
// observe current route to change the icon
// color,label color when navigated
val currentRoute = navBackStackEntry?.destination?.route
// Bottom nav items we declared
Constants.BottomNavItems.forEach { navItem ->
// Place the bottom nav items
BottomNavigationItem(
// it currentRoute is equal then its selected route
selected = currentRoute == navItem.route,
// navigate on click
onClick = {
navController.navigate(navItem.route)
},
// Icon of navItem
icon = {
Icon(imageVector = navItem.icon, contentDescription = navItem.label)
},
// label
label = {
Text(text = navItem.label)
},
alwaysShowLabel = false
)
}
}
}
private lateinit var chromeCast: ChromeCast private lateinit var chromeCast: ChromeCast
private lateinit var viewModel : ChromecastManageViewmodel private lateinit var viewModel: ChromecastManageViewmodel
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
prefs = getSharedPreferences(
"eu.toldi.Caster", Context.MODE_PRIVATE
)
if (chromeCast_ == null) if (chromeCast_ == null)
finish() finish()
chromeCast = chromeCast_ as ChromeCast chromeCast = chromeCast_ as ChromeCast
@ -73,56 +169,74 @@ class ChromecastManagerActivity : ComponentActivity() {
} }
setContent { setContent {
CasterTheme { CasterTheme {
viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java) colorScheme = getColorScheme()
viewModel.chromeCast = chromeCast // remember navController so it does not
// A surface container using the 'background' color from the theme // get recreated on recomposition
Surface(color = MaterialTheme.colors.background) { val navController = rememberNavController()
Column {
//viewModel.fetchMediaStatus()
MenuBar()
Column(
modifier = Modifier
.padding(6.dp)
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
var text by remember { mutableStateOf("") }
OutlinedTextField( Surface(color = colorScheme.background, contentColor = colorScheme.onBackground) {
value = text,
onValueChange = { // Scaffold Component
text = it Scaffold(
}, backgroundColor = colorScheme.background,
label = { Text(stringResource(id = R.string.cast_url)) }, contentColor = colorScheme.onBackground,
modifier = Modifier.padding(vertical = 4.dp) // Bottom navigation
) bottomBar = {
var castEnabled by remember { mutableStateOf(true) } BottomNavigationBar(navController = navController)
Button( },
enabled = castEnabled, content = { padding ->
onClick = { // Navhost: where screens are placed
castEnabled = false NavHostContainer(navController = navController, padding = padding)
viewModel.castLink(text) {
castEnabled = true
}
},
modifier = Modifier.padding(vertical = 4.dp)
) {
Text(text = stringResource(id = R.string.cast))
}
if (castEnabled.not()) {
CircularProgressIndicator()
}
PlayBackControl()
mediaStatus()
} }
} )
} }
} }
} }
} }
@Composable
fun NavHostContainer(
navController: NavHostController,
padding: PaddingValues
) {
NavHost(
navController = navController,
// set the start destination as home
startDestination = "home",
// Set the padding provided by scaffold
modifier = Modifier.padding(paddingValues = padding),
builder = {
// route : Home
composable("home") {
Column {
MenuBar()
HomeScreen()
}
}
// route : search
composable("gallery") {
Column {
MenuBar()
ImageScreen()
}
}
// route : profile
composable("settings") {
SettingsScreen()
}
})
}
@Composable @Composable
fun mediaStatus() { fun mediaStatus() {
val mediaStatus = viewModel.mediaStatus.value val mediaStatus = viewModel.mediaStatus.value
@ -157,13 +271,13 @@ class ChromecastManagerActivity : ComponentActivity() {
val image = val image =
mediaStatus.media.metadata[METADATA_THUMB] as String mediaStatus.media.metadata[METADATA_THUMB] as String
Log.e("Caster", image) Log.e("Caster", image)
Image( AsyncImage(
painter = rememberImagePainter(image), model = image,
contentDescription = null, contentDescription = null,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} }
if(mediaStatus.media.contentType.startsWith("video/")) { if (mediaStatus.media.contentType.startsWith("video/")) {
Row( Row(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
@ -198,13 +312,11 @@ class ChromecastManagerActivity : ComponentActivity() {
@Composable @Composable
fun MenuBar() { fun MenuBar() {
val pickPictureLauncher = rememberLauncherForActivityResult( val pickPictureLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocument() ActivityResultContracts.OpenDocumentTree()
) { fileUri -> ) { fileUri ->
if (fileUri != null) { if (fileUri != null) {
val cacheHelper = FileCacheHelper(applicationContext) DocumentFile.fromTreeUri(applicationContext, fileUri)
cacheHelper.cacheThis(listOf(fileUri)) ?.let { viewModel.addFolder(it) }
viewModel.castFromCache(cacheHelper.tryFileName(fileUri))
} }
} }
TopAppBar( TopAppBar(
@ -222,7 +334,7 @@ class ChromecastManagerActivity : ComponentActivity() {
actions = { actions = {
Row { Row {
IconButton(onClick = { IconButton(onClick = {
pickPictureLauncher.launch(arrayOf("image/*", "video/*")) pickPictureLauncher.launch(Uri.EMPTY)
}) { }) {
Icon( Icon(
Icons.Filled.Folder, Icons.Filled.Folder,
@ -230,7 +342,9 @@ class ChromecastManagerActivity : ComponentActivity() {
) )
} }
} }
} },
backgroundColor = colorScheme.primary,
contentColor = colorScheme.onPrimary
) )
} }
@ -361,4 +475,172 @@ class ChromecastManagerActivity : ComponentActivity() {
} }
return super.onKeyDown(keyCode, event) return super.onKeyDown(keyCode, event)
} }
@Composable
fun HomeScreen() {
// Column Composable,
Column(
modifier = Modifier
.fillMaxSize(),
// Parameters set to place the items in center
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
viewModel =
ViewModelProvider(this@ChromecastManagerActivity).get(ChromecastManageViewmodel::class.java)
viewModel.chromeCast = chromeCast
// A surface container using the 'background' color from the theme
//viewModel.fetchMediaStatus()
Column(
modifier = Modifier
.padding(6.dp)
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
label = { Text(stringResource(id = R.string.cast_url)) },
modifier = Modifier.padding(vertical = 4.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
cursorColor = colorScheme.primary,
focusedBorderColor =
colorScheme.primary.copy(alpha = ContentAlpha.high),
focusedLabelColor = colorScheme.primary
)
)
var castEnabled by remember { mutableStateOf(true) }
Button(
enabled = castEnabled,
colors = ButtonDefaults.buttonColors(
backgroundColor = colorScheme.primary,
contentColor = colorScheme.onPrimary
),
onClick = {
castEnabled = false
val link = text
viewModel.castLink(link) {
if (prefs.getBoolean("sponsorblock_enabled", false)) {
val matcher: Matcher = compiledPattern.matcher(link)
if (matcher.find()) {
val id = matcher.group(1)
Log.e("Caster_SB", id)
val intent = Intent(
applicationContext,
ChromecastManagerService::class.java
).apply {
action = ChromecastManagerService.ACTION_SET_YT_ID
putExtra("yt_id", id)
putExtra("media_url", link)
}
application.startService(intent);
}
}
castEnabled = true
}
},
modifier = Modifier.padding(vertical = 4.dp)
) {
Text(text = stringResource(id = R.string.cast))
}
if (castEnabled.not()) {
CircularProgressIndicator(color = colorScheme.secondary)
}
PlayBackControl()
mediaStatus()
}
}
}
@Composable
fun SettingsScreen() {
// Column Composable,
Column(
modifier = Modifier
.fillMaxSize(),
// parameters set to place the items in center
) {
Row() {
Text("Enable SponsorBlock")
var switched by remember {
mutableStateOf(
prefs.getBoolean(
"sponsorblock_enabled",
false
)
)
}
Switch(checked = switched, onCheckedChange = {
switched = !switched
prefs.edit().putBoolean("sponsorblock_enabled", switched).apply()
})
}
}
}
@Composable
fun ImageScreen() {
// Column Composable,
Column(
modifier = Modifier
.fillMaxSize(),
) {
val folders = viewModel.folderListState.value
val cacheHelper = FileCacheHelper(applicationContext)
LazyColumn() {
items(folders.size) { index ->
Card(modifier = Modifier
.fillMaxWidth()
.clickable {
val intent = Intent(applicationContext, FolderViewActivity::class.java)
FolderViewActivity.chromecastRecv = chromeCast
FolderViewActivity.folderRecv = folders[index]
startActivity(intent)
}) {
Row(
modifier = Modifier
.fillMaxSize(),
// parameters set to place the items in center
verticalAlignment = Alignment.CenterVertically
) {
val image = folders[index].listFiles()
.firstOrNull {
it.name?.endsWith(".png") ?: false || it.name?.endsWith(
".jpg"
) ?: false
}
if (image != null) {
cacheHelper.cacheThis(listOf(image.uri))
val imageFile = File(cacheDir, cacheHelper.tryFileName(image.uri))
Column(
modifier = Modifier
.height(80.dp)
.width(80.dp)
.padding(10.dp)
) {
Log.e(null, imageFile.name.toString())
Image(
painter = rememberImagePainter(imageFile),
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
Text(text = folders[index].name!!)
}
}
}
}
}
}
} }

View file

@ -0,0 +1,201 @@
package eu.toldi.balazs.caster
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role.Companion.Image
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.documentfile.provider.DocumentFile
import coil.compose.AsyncImage
import coil.compose.SubcomposeAsyncImage
import coil.compose.rememberImagePainter
import coil.size.OriginalSize
import coil.transform.CircleCropTransformation
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import eu.toldi.balazs.caster.helpers.FileCacheHelper
import eu.toldi.balazs.caster.services.ChromecastManagerService
import eu.toldi.balazs.caster.ui.theme.CasterTheme
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import su.litvak.chromecast.api.v2.ChromeCast
import java.io.File
class FolderViewActivity : ComponentActivity() {
companion object {
var chromecastRecv: ChromeCast? = null
var folderRecv: DocumentFile? = null
}
@SuppressLint("CoroutineCreationDuringComposition")
@OptIn(ExperimentalPagerApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (chromecastRecv == null || folderRecv == null) finish()
val chromeCast: ChromeCast = chromecastRecv!!
val folder: DocumentFile = folderRecv!!
val cacheHelper = FileCacheHelper(applicationContext)
setContent {
CasterTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Column(modifier = Modifier.fillMaxSize()) {
MenuBar(title = folder.name!!)
val images = folder.listFiles()
.filter { it.name?.endsWith(".png") ?: false || it.name?.endsWith(".jpg") ?: false }
//cacheHelper.cacheThis(images.map { it.uri })
Log.e("CastFile", images.size.toString())
val row_count = images.size / 3 + 1
/*LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(row_count) { index ->
Row(modifier = Modifier.fillMaxWidth()){
for (i in 0 until 3){
if(index+i >= images.size) break
val imageFile = if (images[index+i].exists()) {
cacheHelper.cacheThis(listOf(images[index+i].uri))
File(cacheDir, cacheHelper.tryFileName(images[index+i].uri))
} else {
File(cacheDir, cacheHelper.tryFileName(images[index+i].uri))
}
Card(modifier = Modifier
.padding(all = 5.dp)
.weight(1f)) {
Column {
Column(
modifier = Modifier
.height(100.dp)
.width(100.dp)
.padding(10.dp)
) {
Image(
painter = rememberImagePainter(
data = imageFile
),
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
}
//Text(imageFile.name)
}
}
}
}
}
}*/
val pagerState = rememberPagerState()
if (!pagerState.isScrollInProgress) {
val currentPage = pagerState.currentPage
Log.e("Caster_IMG", currentPage.toString())
GlobalScope.launch(Dispatchers.IO) {
Thread.sleep(1000)
if (pagerState.currentPage == currentPage && !pagerState.isScrollInProgress) {
val imageFile = if (images[currentPage].exists()) {
cacheHelper.cacheThis(listOf(images[currentPage].uri))
File(cacheDir, cacheHelper.tryFileName(images[currentPage].uri))
} else {
File(cacheDir, cacheHelper.tryFileName(images[currentPage].uri))
}
val link = "http:/${ChromeCastHelper.getIPv4Address()}:${ChromecastManagerService.PORT}/assets/${
imageFile.name
}"
Log.e("Caster_IMG","Casting image with url: $link")
val result = ChromeCastHelper.castLink(
link
){ Log.e("Caster_IMG","Casting done")}
}
}
}
HorizontalPager(
modifier = Modifier.fillMaxSize(),
count = images.size,
state = pagerState
) { index ->
val imageFile = if (images[index].exists()) {
cacheHelper.cacheThis(listOf(images[index].uri))
File(cacheDir, cacheHelper.tryFileName(images[index].uri))
} else {
File(cacheDir, cacheHelper.tryFileName(images[index].uri))
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(all = 5.dp)
) {
Column(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.fillMaxWidth()
.height(240.dp)
// .padding(10.dp)
) {
Log.e(
"Caster_IMG",
"http:/${ChromeCastHelper.getIPv4Address()}:${ChromecastManagerService.PORT}/assets/${imageFile.name}"
)
SubcomposeAsyncImage(
model = imageFile,
contentDescription = imageFile.name,
loading = {
CircularProgressIndicator()
},
modifier = Modifier.fillMaxWidth()
)
}
Text(imageFile.name)
}
}
}
}
}
}
}
}
@Composable
fun MenuBar(title: String) {
TopAppBar(
title = {
Text(title)
},
navigationIcon = {
IconButton(onClick = { finish() }) {
Icon(
Icons.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.back)
)
}
}
)
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
CasterTheme {
}
}
}

View file

@ -2,24 +2,39 @@ package eu.toldi.balazs.caster
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent import android.content.Intent
import android.graphics.ColorSpace.adapt
import android.net.wifi.WifiManager import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.MulticastLock import android.net.wifi.WifiManager.MulticastLock
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.Button
import androidx.compose.material.ButtonColors
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Card
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
@ -29,14 +44,17 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.toldi.balazs.caster.model.ChromeCastViewModel import eu.toldi.balazs.caster.model.ChromeCastViewModel
import eu.toldi.balazs.caster.ui.theme.CasterTheme import eu.toldi.balazs.caster.ui.theme.CasterTheme
import eu.toldi.balazs.caster.ui.theme.getColorScheme
import su.litvak.chromecast.api.v2.ChromeCast import su.litvak.chromecast.api.v2.ChromeCast
open class MainActivity : ComponentActivity() { open class MainActivity : ComponentActivity() {
protected lateinit var viewModel: ChromeCastViewModel protected lateinit var viewModel: ChromeCastViewModel
protected var multicastLock: MulticastLock? = null protected var multicastLock: MulticastLock? = null
protected lateinit var colorScheme: ColorScheme
protected fun isViewModelInitialised() = ::viewModel.isInitialized protected fun isViewModelInitialised() = ::viewModel.isInitialized
override fun onStart() { override fun onStart() {
@ -67,13 +85,13 @@ open class MainActivity : ComponentActivity() {
setContent { setContent {
CasterTheme { CasterTheme {
colorScheme = getColorScheme()
viewModel = ViewModelProvider(this).get(ChromeCastViewModel::class.java) viewModel = ViewModelProvider(this).get(ChromeCastViewModel::class.java)
val chromeCastState = viewModel.chromeCasts.observeAsState(initial = emptyList()) val chromeCastState = viewModel.chromeCasts.observeAsState(initial = emptyList())
val chromeCasts = chromeCastState.value val chromeCasts = chromeCastState.value
Log.e(null, chromeCasts.toString()) Log.e(null, chromeCasts.toString())
// 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 = colorScheme.background, contentColor = colorScheme.onBackground) {
Column(modifier = Modifier Column(modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.fillMaxHeight()) { .fillMaxHeight()) {
@ -123,14 +141,14 @@ open class MainActivity : ComponentActivity() {
) { ) {
item { item {
if (chromeCasts.isNotEmpty()) if (chromeCasts.isNotEmpty())
Text(text = stringResource(id = R.string.available_chromecasts)) Text(text = stringResource(id = R.string.available_chromecasts),color =colorScheme.onBackground)
else { else {
Column( Column(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Text(stringResource(id = R.string.looking_for_devices)) Text(text = stringResource(id = R.string.looking_for_devices),color= colorScheme.onBackground)
CircularProgressIndicator() CircularProgressIndicator(color = colorScheme.secondary)
} }
} }
} }
@ -152,7 +170,7 @@ open class MainActivity : ComponentActivity() {
} }
AlertDialog(onDismissRequest = dismiss, AlertDialog(onDismissRequest = dismiss,
title = { title = {
Text(text = stringResource(id = R.string.add_chromecast)) Text(text = stringResource(id = R.string.add_chromecast),color= colorScheme.onBackground)
}, },
text = { text = {
OutlinedTextField( OutlinedTextField(
@ -160,7 +178,7 @@ open class MainActivity : ComponentActivity() {
onValueChange = { onValueChange = {
ipaddress = it ipaddress = it
}, },
label = { Text(stringResource(id = R.string.ip_address)) }, label = { Text(stringResource(id = R.string.ip_address),color= colorScheme.onBackground) },
modifier = Modifier.padding(vertical = 4.dp) modifier = Modifier.padding(vertical = 4.dp)
) )
}, buttons = { }, buttons = {
@ -173,11 +191,16 @@ open class MainActivity : ComponentActivity() {
add(ipaddress) add(ipaddress)
dismiss() dismiss()
}, },
modifier = Modifier.padding(all = 8.dp) colors = ButtonDefaults.buttonColors(backgroundColor = colorScheme.primary, contentColor = colorScheme.onPrimary),
modifier = Modifier.padding(all = 8.dp),
) { ) {
Text(text = stringResource(id = R.string.add_chromecast)) Text(text = stringResource(id = R.string.add_chromecast))
} }
Button(onClick = dismiss, modifier = Modifier.padding(all = 8.dp)) { Button(
onClick = dismiss,
colors = ButtonDefaults.buttonColors(backgroundColor = colorScheme.primary, contentColor = colorScheme.onPrimary),
modifier = Modifier.padding(all = 8.dp)) {
Text(stringResource(id = R.string.cancel)) Text(stringResource(id = R.string.cancel))
} }
} }
@ -221,7 +244,9 @@ open class MainActivity : ComponentActivity() {
) )
} }
Column(modifier = Modifier.fillMaxWidth().height(80.dp), Column(modifier = Modifier
.fillMaxWidth()
.height(80.dp),
verticalArrangement = Arrangement.Center) { verticalArrangement = Arrangement.Center) {
Text( Text(
text = "Name: " + when { text = "Name: " + when {
@ -265,7 +290,9 @@ open class MainActivity : ComponentActivity() {
) )
} }
} }
} },
backgroundColor = colorScheme.primary,
contentColor = colorScheme.onPrimary
) )
} }

View file

@ -4,6 +4,7 @@ import android.net.Uri
import android.util.Log import android.util.Log
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import eu.toldi.balazs.caster.ChromeCastHelper import eu.toldi.balazs.caster.ChromeCastHelper
@ -20,6 +21,9 @@ class ChromecastManageViewmodel : ViewModel() {
private val _mediaState = mutableStateOf<MediaStatus?>(null) private val _mediaState = mutableStateOf<MediaStatus?>(null)
val mediaStatus: State<MediaStatus?> val mediaStatus: State<MediaStatus?>
get() = _mediaState get() = _mediaState
private val _folderListState = mutableStateOf<List<DocumentFile>>(listOf())
val folderListState : State<List<DocumentFile>>
get() = _folderListState
lateinit var chromeCast: ChromeCast lateinit var chromeCast: ChromeCast
@ -107,6 +111,12 @@ class ChromecastManageViewmodel : ViewModel() {
} }
} }
fun addFolder(folder: DocumentFile){
if(folder.isDirectory){
_folderListState.value += folder
}
}
fun getIPv4Address(): InetAddress? { fun getIPv4Address(): InetAddress? {
NetworkInterface.getNetworkInterfaces().toList().forEach { interf -> NetworkInterface.getNetworkInterfaces().toList().forEach { interf ->
interf.inetAddresses.toList().forEach { inetAddress -> interf.inetAddresses.toList().forEach { inetAddress ->

View file

@ -0,0 +1,32 @@
package eu.toldi.balazs.caster.model
import com.beust.klaxon.*
import java.net.URL
private val klaxon = Klaxon()
class SponsorBlock(elements: Collection<SponsorBlockElement>) : ArrayList<SponsorBlockElement>(elements) {
fun toJson() = klaxon.toJsonString(this)
companion object {
fun fromJson(json: String) = SponsorBlock(klaxon.parseArray<SponsorBlockElement>(json)!!)
fun fromID(id: String) = SponsorBlock(klaxon.parseArray<SponsorBlockElement>(URL(url+id).readText())!!)
private const val url = "https://sponsor.ajay.app/api/skipSegments?videoID="
}
}
data class SponsorBlockElement (
val category: String,
val actionType: String,
val segment: List<Double>,
@Json(name = "UUID")
val uuid: String,
val locked: Long,
val votes: Long,
val videoDuration: Double,
val userID: String,
val description: String
)

View file

@ -4,6 +4,7 @@ import android.app.Notification
import android.app.NotificationManager import android.app.NotificationManager
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service import android.app.Service
import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.MediaPlayer import android.media.MediaPlayer
import android.os.Build import android.os.Build
@ -15,7 +16,9 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import eu.toldi.balazs.caster.App.Companion.CHANNEL_ID import eu.toldi.balazs.caster.App.Companion.CHANNEL_ID
import eu.toldi.balazs.caster.ChromeCastHelper import eu.toldi.balazs.caster.ChromeCastHelper
import eu.toldi.balazs.caster.ChromecastManagerActivity
import eu.toldi.balazs.caster.R import eu.toldi.balazs.caster.R
import eu.toldi.balazs.caster.model.SponsorBlock
import io.ktor.application.* import io.ktor.application.*
import io.ktor.features.* import io.ktor.features.*
import io.ktor.http.* import io.ktor.http.*
@ -40,6 +43,7 @@ class ChromecastManagerService : Service() {
const val ACTION_PAUSE = "action_pause" const val ACTION_PAUSE = "action_pause"
const val ACTION_REWIND = "action_rewind" const val ACTION_REWIND = "action_rewind"
const val ACTION_FAST_FORWARD = "action_fast_foward" const val ACTION_FAST_FORWARD = "action_fast_foward"
const val ACTION_SET_YT_ID = "action_set_yt_id"
const val ACTION_NEXT = "action_next" const val ACTION_NEXT = "action_next"
const val ACTION_PREVIOUS = "action_previous" const val ACTION_PREVIOUS = "action_previous"
const val ACTION_STOP = "action_stop" const val ACTION_STOP = "action_stop"
@ -54,6 +58,7 @@ class ChromecastManagerService : Service() {
private lateinit var pendingIntent : PendingIntent private lateinit var pendingIntent : PendingIntent
private var mediaStatus: MediaStatus? = null private var mediaStatus: MediaStatus? = null
private var file : File? = null private var file : File? = null
private var yt_video :YoutubeVideo? = null
override fun onBind(p0: Intent?): IBinder? = null override fun onBind(p0: Intent?): IBinder? = null
private lateinit var chromeCast: ChromeCast private lateinit var chromeCast: ChromeCast
@ -89,11 +94,25 @@ class ChromecastManagerService : Service() {
ACTION_PAUSE -> mController.transportControls.pause() ACTION_PAUSE -> mController.transportControls.pause()
ACTION_FAST_FORWARD -> mController.transportControls.fastForward() ACTION_FAST_FORWARD -> mController.transportControls.fastForward()
ACTION_REWIND -> mController.transportControls.rewind() ACTION_REWIND -> mController.transportControls.rewind()
ACTION_SET_YT_ID -> setYTID(intent)
} }
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
private fun setYTID(intent: Intent) {
CoroutineScope(IO).launch {
try {
val id = intent.getStringExtra("yt_id")!!
val url = intent.getStringExtra("media_url")!!
yt_video = YoutubeVideo(id,url, SponsorBlock.fromID(id))
Log.e(null,yt_video.toString())
}catch (e: Exception) {
Log.e("CasterService",e.stackTraceToString())
}
}
}
private fun initAction(intent: Intent?){ private fun initAction(intent: Intent?){
initMediaSessions() initMediaSessions()
@ -104,42 +123,91 @@ class ChromecastManagerService : Service() {
Log.e(null, address) Log.e(null, address)
chromeCast = ChromeCast(address) chromeCast = ChromeCast(address)
chromeCast.name = name chromeCast.name = name
ChromecastManagerActivity.chromeCast_ = chromeCast
val notificationIntent = Intent(this, ChromecastManagerService::class.java) val notificationIntent = Intent(this, ChromecastManagerService::class.java)
pendingIntent = pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_MUTABLE) PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID) val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Caster for ${chromeCast.name}@${address}") .setContentTitle("Caster for ${chromeCast.name}@${address}")
.setSmallIcon(R.drawable.ic_caster_logo) .setSmallIcon(R.drawable.ic_new_caster_monochrome_foreground)
.setContentIntent(pendingIntent).build() .setContentIntent(pendingIntent).build()
startForeground(1, notification) startForeground(1, notification)
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
server.start(wait = true) var started = false
while (!started) {
try {
server.start(wait = true)
started = true
Thread.sleep(1000)
} catch (e: Exception) {
Log.e(null,e.stackTraceToString())
}
}
} }
val prefs = getSharedPreferences(
"eu.toldi.Caster", Context.MODE_PRIVATE)
CoroutineScope(IO).launch { CoroutineScope(IO).launch {
var errorCount = 0
while (true) { while (true) {
try { try {
ChromeCastHelper.chromeCast = chromeCast ChromeCastHelper.chromeCast = chromeCast
var status = chromeCast.status var status = chromeCast.status
Log.d(null, status.applications.toString()) Log.d(null, status.applications.toString())
if(status.runningApp.id == ChromeCastHelper.APP_BACKDROP)
errorCount++
val status_message = if (status.runningApp != null) { val status_message = if (status.runningApp != null) {
ChromeCastHelper.getApplicationDisplayName(status.runningApp.id) ChromeCastHelper.getApplicationDisplayName(status.runningApp.id)
} else { } else {
"${chromeCast.name} is ready for casting" "${chromeCast.name} is ready for casting"
} }
mediaStatus = ChromeCastHelper.fetchMediaStatus() mediaStatus = ChromeCastHelper.fetchMediaStatus()
Log.e("Caster_Service","Sb enabled: "+prefs.getBoolean("sponsorblock_enabled",false).toString()+ " yt: ${yt_video.toString()}")
if(prefs.getBoolean("sponsorblock_enabled",false) && yt_video != null){
try {
if(yt_video!!.url.startsWith("https://youtu.be/")){
yt_video!!.url = mediaStatus!!.media.url
}
if (mediaStatus!!.media.url != yt_video!!.url) {
yt_video = null
}
checkForSponsorBlock()
} catch (e:Exception){
//yt_video = null
}
}else {
yt_video = null
}
errorCount = 0
buildNotification() buildNotification()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(null,e.stackTraceToString()) Log.e(null,e.stackTraceToString())
errorCount++
} }
Thread.sleep(10500) if(errorCount > 120){
stopForeground(true)
break
}
Thread.sleep(1000)
} }
} }
} }
private fun checkForSponsorBlock() {
try {
yt_video!!.sponsorBlock.forEach {
if (mediaStatus!!.currentTime >= it.segment[0] && mediaStatus!!.currentTime <= it.segment[1])
chromeCast.seek(it.segment[1])
}
}catch (e: Exception) {
Log.e(null,e.stackTraceToString())
}
}
private fun generateAction( private fun generateAction(
icon: Int, icon: Int,
title: String, title: String,
@ -147,7 +215,7 @@ class ChromecastManagerService : Service() {
): NotificationCompat.Action { ): NotificationCompat.Action {
val intent = Intent(applicationContext, ChromecastManagerService::class.java) val intent = Intent(applicationContext, ChromecastManagerService::class.java)
intent.action = intentAction intent.action = intentAction
val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, 0) val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Action(icon,title,pendingIntent) return NotificationCompat.Action(icon,title,pendingIntent)
} }
@ -313,4 +381,10 @@ class ChromecastManagerService : Service() {
} }
) )
} }
data class YoutubeVideo(
val id: String,
var url: String,
val sponsorBlock: SponsorBlock
)
} }

View file

@ -0,0 +1,35 @@
package eu.toldi.balazs.caster.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
@Composable
fun getColorScheme(): ColorScheme {
val LightColorScheme = lightColorScheme(
primary = MaterialTheme.colors.primary,
secondary = MaterialTheme.colors.secondary,
tertiary = MaterialTheme.colors.secondaryVariant,
// error, primaryContainer, onSecondary, etc.
)
val DarkColorScheme = darkColorScheme(
primary = MaterialTheme.colors.primary,
secondary = MaterialTheme.colors.secondary,
tertiary = MaterialTheme.colors.secondaryVariant,
// error, primaryContainer, onSecondary, etc.
)
val darkTheme = isSystemInDarkTheme()
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
return when {
dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
}

View file

@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.101828575"
android:scaleY="0.101828575"
android:translateX="27.835714"
android:translateY="29.485714">
<path
android:pathData="M125.67,37.64h361.61v220.93h-361.61z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#ffe680"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M217.94,298.36h177.75v54.12h-177.75z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#b3b3b3"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M282.15,261.97h48.97v36.25h-48.97z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#999999"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m27.62,354.13a63.86,63.86 0,0 1,82.15 3.48,63.86 63.86,0 0,1 10.64,81.53"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m34.3,401.3a27.68,30.75 90,0 1,39.56 1.51,27.68 30.75,90 0,1 5.12,35.34"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m31.57,307.13a97.94,97.94 45,0 1,125.99 5.33,97.94 97.94,0 0,1 16.32,125.04"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View file

@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.101828575"
android:scaleY="0.101828575"
android:translateX="27.835714"
android:translateY="29.485714">
<path
android:pathData="M125.67,37.64h361.61v220.93h-361.61z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M217.94,298.36h177.75v54.12h-177.75z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M282.15,261.97h48.97v36.25h-48.97z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m27.62,354.13a63.86,63.86 0,0 1,82.15 3.48,63.86 63.86,0 0,1 10.64,81.53"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m34.3,401.3a27.68,30.75 90,0 1,39.56 1.51,27.68 30.75,90 0,1 5.12,35.34"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m31.57,307.13a97.94,97.94 45,0 1,125.99 5.33,97.94 97.94,0 0,1 16.32,125.04"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_new_caster_background"/>
<foreground android:drawable="@drawable/ic_new_caster_foreground"/>
<monochrome android:drawable="@drawable/ic_new_caster_monochrome_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_new_caster_background"/>
<foreground android:drawable="@drawable/ic_new_caster_foreground"/>
<monochrome android:drawable="@drawable/ic_new_caster_monochrome_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_adaptive_background">#AAD400</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_new_caster_background">#AAD400</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_new_caster_monochrome_background">#AAD400</color>
</resources>

View file

@ -18,5 +18,6 @@
<string name="back">Back</string> <string name="back">Back</string>
<string name="now_playing">Now playing</string> <string name="now_playing">Now playing</string>
<string name="cast_url">Cast URL</string> <string name="cast_url">Cast URL</string>
<string name="title_activity_folder_view">FolderViewActivity</string>
</resources> </resources>