1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Fixed headers and removed deprecations.

This commit is contained in:
Koen 2024-01-04 13:20:24 +01:00
parent 28886045af
commit 2cd9db028d
11 changed files with 85 additions and 76 deletions

View file

@ -85,10 +85,13 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation "com.squareup.okhttp3:okhttp:4.11.0"
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
implementation 'org.java-websocket:Java-WebSocket:1.5.4'
implementation 'androidx.media3:media3-ui:1.2.0'
implementation 'androidx.media3:media3-exoplayer-dash:1.2.0'
implementation 'androidx.media3:media3-exoplayer-hls:1.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

View file

@ -31,6 +31,7 @@ class BootReceiver : BroadcastReceiver() {
}
}
@Suppress("DEPRECATION")
private fun createNotificationBuilder(context: Context): NotificationCompat.Builder {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationCompat.Builder(context, CHANNEL_ID)

View file

@ -2,7 +2,6 @@ package com.futo.fcast.receiver
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.media3.ui.PlayerView
class CustomStyledPlayerView(context: Context, attrs: AttributeSet? = null) : StyledPlayerView(context, attrs) { }
class CustomPlayerView(context: Context, attrs: AttributeSet? = null) : PlayerView(context, attrs) { }

View file

@ -4,6 +4,7 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.PackageInstaller
import android.os.Build
import android.util.Log
class InstallReceiver : BroadcastReceiver() {
@ -13,7 +14,13 @@ class InstallReceiver : BroadcastReceiver() {
when (status) {
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
val activityIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
val activityIntent: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(Intent.EXTRA_INTENT)
}
if (activityIntent == null) {
Log.w(TAG, "Received STATUS_PENDING_USER_ACTION and activity intent is null.")
return

View file

@ -2,6 +2,7 @@ package com.futo.fcast.receiver
import WebSocketListenerService
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.app.PendingIntent
import android.content.Context
@ -19,14 +20,17 @@ import android.util.TypedValue
import android.view.View
import android.view.WindowManager
import android.widget.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ui.StyledPlayerView
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.google.zxing.BarcodeFormat
import com.journeyapps.barcodescanner.BarcodeEncoder
import kotlinx.coroutines.*
@ -46,27 +50,38 @@ class MainActivity : AppCompatActivity() {
private lateinit var _updateSpinner: ImageView
private lateinit var _imageSpinner: ImageView
private lateinit var _layoutConnectionInfo: ConstraintLayout
private lateinit var _videoBackground: StyledPlayerView
private lateinit var _videoBackground: PlayerView
private lateinit var _viewDemo: View
private lateinit var _player: ExoPlayer
private lateinit var _imageQr: ImageView
private lateinit var _textScanToConnect: TextView
private lateinit var _systemAlertWindowPermissionLauncher: ActivityResultLauncher<Intent>
private var _updateAvailable: Boolean? = null
private var _updating: Boolean = false
private var _demoClickCount = 0
private var _lastDemoToast: Toast? = null
private val _preferenceFileKey get() = "$packageName.PREFERENCE_FILE_KEY"
private val _scope: CoroutineScope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null && savedInstanceState.containsKey("updateAvailable")) {
_updateAvailable = savedInstanceState.getBoolean("updateAvailable", false)
_systemAlertWindowPermissionLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { _ ->
if (Settings.canDrawOverlays(this)) {
// Permission granted, you can launch the activity from the foreground service
Toast.makeText(this, "Alert window permission granted", Toast.LENGTH_LONG).show()
Log.i(TAG, "Alert window permission granted")
} else {
// Permission denied, notify the user and request again if necessary
Toast.makeText(this, "Permission is required to work in background", Toast.LENGTH_LONG).show()
Log.i(TAG, "Alert window permission denied")
}
}
_updateAvailable = if (savedInstanceState != null && savedInstanceState.containsKey("updateAvailable")) {
savedInstanceState.getBoolean("updateAvailable", false)
} else {
_updateAvailable = null
null
}
_buttonUpdate = findViewById(R.id.button_update)
@ -118,13 +133,13 @@ class MainActivity : AppCompatActivity() {
_updateSpinner.visibility = View.VISIBLE
(_updateSpinner.drawable as Animatable?)?.start()
_scope.launch(Dispatchers.IO) {
lifecycleScope.launch(Dispatchers.IO) {
checkForUpdates()
}
}
val ips = getIPs()
_textIPs.text = "IPs\n" + ips.joinToString("\n") + "\n\nPorts\n${TcpListenerService.PORT} (TCP), ${WebSocketListenerService.PORT} (WS)"
_textIPs.text = "IPs\n${ips.joinToString("\n")}\n\nPorts\n${TcpListenerService.PORT} (TCP), ${WebSocketListenerService.PORT} (WS)"
try {
val barcodeEncoder = BarcodeEncoder()
@ -168,7 +183,6 @@ class MainActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
InstallReceiver.onReceiveResult = null
_scope.cancel()
_player.release()
NetworkService.activityCount--
}
@ -179,11 +193,7 @@ class MainActivity : AppCompatActivity() {
}
private fun restartService() {
val i = NetworkService.instance
if (i != null) {
i.stopSelf()
}
NetworkService.instance?.stopSelf()
startService(Intent(this, NetworkService::class.java))
}
@ -245,7 +255,7 @@ class MainActivity : AppCompatActivity() {
.setPositiveButton(R.string.permission_dialog_positive_button) { _, _ ->
try {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, REQUEST_CODE)
_systemAlertWindowPermissionLauncher.launch(intent)
} catch (e: Throwable) {
Log.e("OverlayPermission", "Error requesting overlay permission", e)
Toast.makeText(this, "An error occurred: ${e.message}", Toast.LENGTH_LONG).show()
@ -271,23 +281,6 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE) {
if (Settings.canDrawOverlays(this)) {
// Permission granted, you can launch the activity from the foreground service
Toast.makeText(this, "Alert window permission granted", Toast.LENGTH_LONG).show()
Log.i(TAG, "Alert window permission granted")
} else {
// Permission denied, notify the user and request again if necessary
Toast.makeText(this, "Permission is required to work in background", Toast.LENGTH_LONG).show()
Log.i(TAG, "Alert window permission denied")
}
}
super.onActivityResult(requestCode, resultCode, data)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
@ -410,7 +403,7 @@ class MainActivity : AppCompatActivity() {
setText(resources.getText(R.string.downloading_update))
(_updateSpinner.drawable as Animatable?)?.start()
_scope.launch(Dispatchers.IO) {
lifecycleScope.launch(Dispatchers.IO) {
var inputStream: InputStream? = null
try {
val client = OkHttpClient()
@ -458,8 +451,7 @@ class MainActivity : AppCompatActivity() {
if (lastProgressText != progressText) {
lastProgressText = progressText
//TODO: Use proper scope
GlobalScope.launch(Dispatchers.Main) {
lifecycleScope.launch(Dispatchers.Main) {
_textProgress.text = progressText
}
}
@ -504,7 +496,7 @@ class MainActivity : AppCompatActivity() {
(_updateSpinner.drawable as Animatable?)?.stop()
if (result == null || result.isBlank()) {
if (result.isNullOrBlank()) {
_updateSpinner.setImageResource(R.drawable.ic_update_success)
setText(resources.getText(R.string.success))
} else {

View file

@ -11,6 +11,8 @@ import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
class NetworkService : Service() {
private var _discoveryService: DiscoveryService? = null
@ -208,11 +210,7 @@ class NetworkService : Service() {
if (PlayerActivity.instance == null) {
val i = Intent(this@NetworkService, PlayerActivity::class.java)
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
i.putExtra("container", playMessage.container)
i.putExtra("url", playMessage.url)
i.putExtra("content", playMessage.content)
i.putExtra("time", playMessage.time)
i.putExtra("speed", playMessage.speed)
i.putExtra("message", Json.encodeToString(playMessage))
if (activityCount > 0) {
startActivity(i)

View file

@ -16,25 +16,28 @@ import android.view.WindowInsets
import android.view.WindowManager
import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import com.google.android.exoplayer2.ExoPlayer
import com.google.android.exoplayer2.MediaItem
import com.google.android.exoplayer2.PlaybackException
import com.google.android.exoplayer2.PlaybackParameters
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory
import com.google.android.exoplayer2.source.dash.DashMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.StyledPlayerView
import com.google.android.exoplayer2.upstream.DefaultDataSource
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
import com.google.android.exoplayer2.upstream.HttpDataSource
import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.DefaultDataSource
import androidx.media3.datasource.DefaultHttpDataSource
import androidx.media3.datasource.HttpDataSource
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.dash.DashMediaSource
import androidx.media3.exoplayer.hls.HlsMediaSource
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.ui.PlayerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.File
import java.io.FileOutputStream
import kotlin.math.abs
@ -42,7 +45,7 @@ import kotlin.math.max
class PlayerActivity : AppCompatActivity() {
private lateinit var _playerControlView: StyledPlayerView
private lateinit var _playerControlView: PlayerView
private lateinit var _imageSpinner: ImageView
private lateinit var _textMessage: TextView
private lateinit var _layoutOverlay: ConstraintLayout
@ -165,6 +168,7 @@ class PlayerActivity : AppCompatActivity() {
}
}
@OptIn(UnstableApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i(TAG, "onCreate")
@ -203,13 +207,15 @@ class PlayerActivity : AppCompatActivity() {
.build()
_connectivityManager.registerNetworkCallback(netReq, _connectivityEvents)
val container = intent.getStringExtra("container") ?: ""
val url = intent.getStringExtra("url")
val content = intent.getStringExtra("content")
val time = intent.getDoubleExtra("time", 0.0)
val speed = intent.getDoubleExtra("speed", 1.0)
play(PlayMessage(container, url, content, time, speed))
val playMessage = intent.getStringExtra("message")?.let {
try {
Json.decodeFromString<PlayMessage>(it)
} catch (e: Throwable) {
Log.i(TAG, "Failed to deserialize play message.", e)
null
}
}
playMessage?.let { play(it) }
instance = this
NetworkService.activityCount++
@ -290,6 +296,7 @@ class PlayerActivity : AppCompatActivity() {
NetworkService.activityCount--
}
@OptIn(UnstableApi::class)
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (_playerControlView.isControllerFullyVisible) {
if (event.keyCode == KeyEvent.KEYCODE_BACK) {
@ -312,6 +319,7 @@ class PlayerActivity : AppCompatActivity() {
return super.dispatchKeyEvent(event)
}
@OptIn(UnstableApi::class)
fun play(playMessage: PlayMessage) {
val mediaItemBuilder = MediaItem.Builder()
if (playMessage.container.isNotEmpty()) {

View file

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#000000">
<com.google.android.exoplayer2.ui.StyledPlayerView
<androidx.media3.ui.PlayerView
android:id="@+id/video_background"
app:use_controller="false"
android:layout_width="match_parent"
@ -59,7 +59,7 @@
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:text="Waiting for a connection"
android:text="@string/waiting_for_media"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -7,7 +7,7 @@
android:layout_height="match_parent"
android:background="#000000">
<com.google.android.exoplayer2.ui.StyledPlayerView
<androidx.media3.ui.PlayerView
android:id="@+id/video_background"
app:use_controller="false"
android:layout_width="match_parent"
@ -59,7 +59,7 @@
android:gravity="center"
android:fontFamily="@font/inter_regular"
android:textSize="14sp"
android:text="Waiting for a connection"
android:text="@string/waiting_for_media"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -6,7 +6,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/black">
<com.futo.fcast.receiver.CustomStyledPlayerView
<com.futo.fcast.receiver.CustomPlayerView
android:id="@+id/player_control_view"
android:layout_width="match_parent"
android:layout_height="match_parent"

View file

@ -21,4 +21,5 @@
<string name="permission_dialog_message">This app requires the System Alert Window permission to display content on top of other apps. Please grant the permission.</string>
<string name="permission_dialog_positive_button">Allow</string>
<string name="permission_dialog_negative_button">Cancel</string>
<string name="waiting_for_media">Waiting for media</string>
</resources>