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:
parent
28886045af
commit
2cd9db028d
11 changed files with 85 additions and 76 deletions
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) { }
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
Loading…
Add table
Add a link
Reference in a new issue