Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin 2024-09-03 22:33:04 +02:00
commit ea59f8dccb
12 changed files with 145 additions and 84 deletions

View file

@ -5,7 +5,6 @@
<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.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:ignore="ScopedStorage" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:ignore="ScopedStorage" />

View file

@ -77,11 +77,15 @@ class AdvancedOrientationListener(private val activity: Activity, private val li
lastStableOrientation = newOrientation lastStableOrientation = newOrientation
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
try {
delay(stabilityThresholdTime) delay(stabilityThresholdTime)
if (newOrientation == lastStableOrientation) { if (newOrientation == lastStableOrientation) {
lastOrientation = newOrientation lastOrientation = newOrientation
onOrientationChanged.emit(newOrientation) onOrientationChanged.emit(newOrientation)
} }
} catch (e: Throwable) {
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
}
} }
} }
} }
@ -111,4 +115,8 @@ class AdvancedOrientationListener(private val activity: Activity, private val li
fun stopListening() { fun stopListening() {
sensorManager.unregisterListener(sensorListener) sensorManager.unregisterListener(sensorListener)
} }
companion object {
private val TAG = "AdvancedOrientationListener"
}
} }

View file

@ -487,7 +487,7 @@ class Settings : FragmentedStorageFileJson() {
class CommentSettings { class CommentSettings {
@FormField(R.string.default_comment_section, FieldForm.DROPDOWN, -1, 0) @FormField(R.string.default_comment_section, FieldForm.DROPDOWN, -1, 0)
@DropdownFieldOptionsId(R.array.comment_sections) @DropdownFieldOptionsId(R.array.comment_sections)
var defaultCommentSection: Int = 0; var defaultCommentSection: Int = 1;
@FormField(R.string.bad_reputation_comments_fading, FieldForm.TOGGLE, R.string.bad_reputation_comments_fading_description, 0) @FormField(R.string.bad_reputation_comments_fading, FieldForm.TOGGLE, R.string.bad_reputation_comments_fading_description, 0)
var badReputationCommentsFading: Boolean = true; var badReputationCommentsFading: Boolean = true;

View file

@ -5,6 +5,7 @@ import android.content.pm.ActivityInfo
import android.hardware.SensorManager import android.hardware.SensorManager
import android.view.OrientationEventListener import android.view.OrientationEventListener
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -34,11 +35,15 @@ class SimpleOrientationListener(
lastStableOrientation = newOrientation lastStableOrientation = newOrientation
lifecycleScope.launch(Dispatchers.Main) { lifecycleScope.launch(Dispatchers.Main) {
try {
delay(stabilityThresholdTime) delay(stabilityThresholdTime)
if (newOrientation == lastStableOrientation) { if (newOrientation == lastStableOrientation) {
lastOrientation = newOrientation lastOrientation = newOrientation
onOrientationChanged.emit(newOrientation) onOrientationChanged.emit(newOrientation)
} }
} catch (e: Throwable) {
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
}
} }
} }
} }
@ -52,4 +57,8 @@ class SimpleOrientationListener(
fun stopListening() { fun stopListening() {
orientationListener.disable() orientationListener.disable()
} }
companion object {
private val TAG = "SimpleOrientationListener"
}
} }

View file

@ -147,8 +147,6 @@ fun InputStream.copyToOutputStream(inputStreamLength: Long, outputStream: Output
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
fun Activity.setNavigationBarColorAndIcons() { fun Activity.setNavigationBarColorAndIcons() {
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.black); window.navigationBarColor = ContextCompat.getColor(this, android.R.color.black);
if (Settings.instance.playback.allowVideoToGoUnderCutout)
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
window.insetsController?.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS); window.insetsController?.setSystemBarsAppearance(0, WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS);

View file

@ -14,6 +14,7 @@ import android.os.StrictMode.VmPolicy
import android.util.Log import android.util.Log
import android.util.TypedValue import android.util.TypedValue
import android.view.View import android.view.View
import android.view.WindowManager
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
@ -112,7 +113,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
private lateinit var _overlayContainer: FrameLayout; private lateinit var _overlayContainer: FrameLayout;
private lateinit var _toastView: ToastView; private lateinit var _toastView: ToastView;
private lateinit var _multicastLock: WifiManager.MulticastLock
//Segment Containers //Segment Containers
private lateinit var _fragContainerTopBar: FragmentContainerView; private lateinit var _fragContainerTopBar: FragmentContainerView;
@ -247,12 +247,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Logger.i(TAG, "Acquiring multicast lock")
val wifiManager = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
_multicastLock = wifiManager.createMulticastLock("mdnsLock")
_multicastLock.setReferenceCounted(true)
_multicastLock.acquire()
Logger.i(TAG, "MainActivity Starting"); Logger.i(TAG, "MainActivity Starting");
StateApp.instance.setGlobalContext(this, lifecycleScope); StateApp.instance.setGlobalContext(this, lifecycleScope);
StateApp.instance.mainAppStarting(this); StateApp.instance.mainAppStarting(this);
@ -260,7 +254,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
setNavigationBarColorAndIcons(); setNavigationBarColorAndIcons();
if (Settings.instance.playback.allowVideoToGoUnderCutout)
window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
runBlocking { runBlocking {
StatePlatform.instance.updateAvailableClients(this@MainActivity); StatePlatform.instance.updateAvailableClients(this@MainActivity);
@ -977,7 +972,6 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy(); super.onDestroy();
Logger.v(TAG, "onDestroy") Logger.v(TAG, "onDestroy")
_multicastLock.release()
StateApp.instance.mainAppDestroyed(this); StateApp.instance.mainAppDestroyed(this);
} }

View file

@ -2,6 +2,7 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
@ -10,6 +11,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.WindowInsets import android.view.WindowInsets
import android.view.WindowInsetsController import android.view.WindowInsetsController
import android.view.WindowManager
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -426,23 +428,43 @@ class VideoDetailFragment : MainFragment {
onMaximized.clear(); onMaximized.clear();
} }
private fun hideSystemUI() { private fun hideSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false) WindowCompat.setDecorFitsSystemWindows(requireActivity().window, false)
activity?.window?.insetsController?.let { controller -> activity?.window?.insetsController?.let { controller ->
controller.hide(WindowInsets.Type.statusBars()) controller.hide(WindowInsets.Type.statusBars())
controller.hide(WindowInsets.Type.systemBars()) controller.hide(WindowInsets.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} }
} else {
@Suppress("DEPRECATION")
activity?.window?.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
@Suppress("DEPRECATION")
activity?.window?.decorView?.systemUiVisibility = (
View.SYSTEM_UI_FLAG_FULLSCREEN
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
)
}
} }
private fun showSystemUI() { private fun showSystemUI() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true) WindowCompat.setDecorFitsSystemWindows(requireActivity().window, true)
activity?.window?.insetsController?.let { controller -> activity?.window?.insetsController?.let { controller ->
controller.show(WindowInsets.Type.statusBars()) controller.show(WindowInsets.Type.statusBars())
controller.show(WindowInsets.Type.systemBars()) controller.show(WindowInsets.Type.systemBars())
controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT controller.systemBarsBehavior = WindowInsetsController.BEHAVIOR_DEFAULT
} }
} else {
@Suppress("DEPRECATION")
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)
@Suppress("DEPRECATION")
activity?.window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
} }
private fun onFullscreenChanged(fullscreen : Boolean) { private fun onFullscreenChanged(fullscreen : Boolean) {

View file

@ -232,7 +232,7 @@ class MDNSListener {
private fun receiveLoop(client: DatagramSocket) { private fun receiveLoop(client: DatagramSocket) {
Logger.i(TAG, "Started receive loop") Logger.i(TAG, "Started receive loop")
val buffer = ByteArray(1024) val buffer = ByteArray(8972)
val packet = DatagramPacket(buffer, buffer.size) val packet = DatagramPacket(buffer, buffer.size)
while (_started) { while (_started) {
try { try {

View file

@ -10,7 +10,6 @@ import com.futo.platformplayer.constructs.Event1
class MediaControlReceiver : BroadcastReceiver() { class MediaControlReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent?) {
val act = intent?.getStringExtra(EXTRA_MEDIA_ACTION); val act = intent?.getStringExtra(EXTRA_MEDIA_ACTION);
Logger.i(TAG, "Received MediaControl Event $act"); Logger.i(TAG, "Received MediaControl Event $act");

View file

@ -55,9 +55,15 @@ class MediaPlaybackService : Service() {
private var _notificationChannel: NotificationChannel? = null; private var _notificationChannel: NotificationChannel? = null;
private var _mediaSession: MediaSessionCompat? = null; private var _mediaSession: MediaSessionCompat? = null;
private var _hasFocus: Boolean = false; private var _hasFocus: Boolean = false;
private var _isTransientLoss: Boolean = false;
private var _focusRequest: AudioFocusRequest? = null; private var _focusRequest: AudioFocusRequest? = null;
private var _audioFocusLossTime_ms: Long? = null private var _audioFocusLossTime_ms: Long? = null
private var _playbackState = PlaybackStateCompat.STATE_NONE; private var _playbackState = PlaybackStateCompat.STATE_NONE;
private var _lastAudioFocusAttempt_ms: Long? = null
private val isPlaying get() = _playbackState != PlaybackStateCompat.STATE_PAUSED &&
_playbackState != PlaybackStateCompat.STATE_STOPPED &&
_playbackState != PlaybackStateCompat.STATE_NONE &&
_playbackState != PlaybackStateCompat.STATE_ERROR
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Logger.v(TAG, "onStartCommand"); Logger.v(TAG, "onStartCommand");
@ -159,12 +165,7 @@ class MediaPlaybackService : Service() {
Logger.v(TAG, "closeMediaSession"); Logger.v(TAG, "closeMediaSession");
stopForeground(STOP_FOREGROUND_REMOVE); stopForeground(STOP_FOREGROUND_REMOVE);
val focusRequest = _focusRequest; abandonAudioFocus()
if (focusRequest != null) {
_audioManager?.abandonAudioFocusRequest(focusRequest);
_focusRequest = null;
}
_hasFocus = false;
val notifManager = _notificationManager; val notifManager = _notificationManager;
Logger.i(TAG, "Cancelling playback notification (notifManager: ${notifManager != null})"); Logger.i(TAG, "Cancelling playback notification (notifManager: ${notifManager != null})");
@ -335,29 +336,73 @@ class MediaPlaybackService : Service() {
.setState(state, pos, 1f, SystemClock.elapsedRealtime()) .setState(state, pos, 1f, SystemClock.elapsedRealtime())
.build()); .build());
if(_focusRequest == null)
setAudioFocus();
_playbackState = state; _playbackState = state;
try {
setAudioFocus()
} catch (e: Throwable) {
Logger.e(TAG, "Failed to set audio focus", e)
}
} }
//TODO: (TBD) This code probably more fitting inside FutoVideoPlayer, as this service is generally only used for global events //TODO: (TBD) This code probably more fitting inside FutoVideoPlayer, as this service is generally only used for global events
private fun setAudioFocus() { private fun setAudioFocus() {
Log.i(TAG, "Requested audio focus."); if (!isPlaying) {
return
}
if (_hasFocus || _isTransientLoss) {
return;
}
val now = System.currentTimeMillis()
val lastAudioFocusAttempt_ms = _lastAudioFocusAttempt_ms
if (lastAudioFocusAttempt_ms == null || now - lastAudioFocusAttempt_ms > 1000) {
_lastAudioFocusAttempt_ms = now
} else {
Log.v(TAG, "Skipped trying to get audio focus because gaining audio focus was recently attempted.");
return
}
if (_focusRequest == null) {
val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
.setAcceptsDelayedFocusGain(true) .setAcceptsDelayedFocusGain(true)
.setOnAudioFocusChangeListener(_audioFocusChangeListener) .setOnAudioFocusChangeListener(_audioFocusChangeListener)
.build() .build()
_focusRequest = focusRequest; _focusRequest = focusRequest;
val result = _audioManager?.requestAudioFocus(focusRequest) Log.i(TAG, "Created audio focus request.");
}
Log.i(TAG, "Requesting audio focus.");
val result = _audioManager?.requestAudioFocus(_focusRequest!!)
Log.i(TAG, "Audio focus request result $result"); Log.i(TAG, "Audio focus request result $result");
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
//TODO: Handle when not possible to get audio focus _hasFocus = true
_hasFocus = true; _isTransientLoss = false
Log.i(TAG, "Audio focus received"); Log.i(TAG, "Audio focus received");
} else if (result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
_hasFocus = false
_isTransientLoss = false
Log.i(TAG, "Audio focus delayed, waiting for focus")
} else {
_hasFocus = false
_isTransientLoss = false
Log.i(TAG, "Audio focus not granted, retrying later")
} }
Log.i(TAG, "Audio focus requested.");
}
private fun abandonAudioFocus() {
val focusRequest = _focusRequest;
if (focusRequest != null) {
Logger.i(TAG, "Audio focus abandoned")
_audioManager?.abandonAudioFocusRequest(focusRequest);
_focusRequest = null;
}
_hasFocus = false;
_isTransientLoss = false;
} }
private val _audioFocusChangeListener = private val _audioFocusChangeListener =
@ -365,9 +410,8 @@ class MediaPlaybackService : Service() {
try { try {
when (focusChange) { when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> { AudioManager.AUDIOFOCUS_GAIN -> {
//Do not start playing on gaining audo focus
//MediaControlReceiver.onPlayReceived.emit();
_hasFocus = true; _hasFocus = true;
_isTransientLoss = false;
Log.i(TAG, "Audio focus gained (restartPlaybackAfterLoss = ${Settings.instance.playback.restartPlaybackAfterLoss}, _audioFocusLossTime_ms = $_audioFocusLossTime_ms)"); Log.i(TAG, "Audio focus gained (restartPlaybackAfterLoss = ${Settings.instance.playback.restartPlaybackAfterLoss}, _audioFocusLossTime_ms = $_audioFocusLossTime_ms)");
if (Settings.instance.playback.restartPlaybackAfterLoss == 1) { if (Settings.instance.playback.restartPlaybackAfterLoss == 1) {
@ -385,40 +429,28 @@ class MediaPlaybackService : Service() {
} }
} }
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
MediaControlReceiver.onPauseReceived.emit(); if (isPlaying) {
if (_playbackState != PlaybackStateCompat.STATE_PAUSED &&
_playbackState != PlaybackStateCompat.STATE_STOPPED &&
_playbackState != PlaybackStateCompat.STATE_NONE &&
_playbackState != PlaybackStateCompat.STATE_ERROR) {
_audioFocusLossTime_ms = System.currentTimeMillis()
}
Log.i(TAG, "Audio focus transient loss");
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
Log.i(TAG, "Audio focus transient loss, can duck");
}
AudioManager.AUDIOFOCUS_LOSS -> {
if (_playbackState != PlaybackStateCompat.STATE_PAUSED &&
_playbackState != PlaybackStateCompat.STATE_STOPPED &&
_playbackState != PlaybackStateCompat.STATE_NONE &&
_playbackState != PlaybackStateCompat.STATE_ERROR) {
_audioFocusLossTime_ms = System.currentTimeMillis() _audioFocusLossTime_ms = System.currentTimeMillis()
} }
_hasFocus = false; _hasFocus = false;
_isTransientLoss = true;
MediaControlReceiver.onPauseReceived.emit(); MediaControlReceiver.onPauseReceived.emit();
Log.i(TAG, "Audio focus lost"); Log.i(TAG, "Audio focus transient loss (_audioFocusLossTime_ms = ${_audioFocusLossTime_ms})");
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
Log.i(TAG, "Audio focus transient loss, can duck");
_hasFocus = true;
_isTransientLoss = true;
}
AudioManager.AUDIOFOCUS_LOSS -> {
if (isPlaying) {
_audioFocusLossTime_ms = System.currentTimeMillis()
}
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager MediaControlReceiver.onPauseReceived.emit();
val runningAppProcesses = activityManager.runningAppProcesses abandonAudioFocus();
for (processInfo in runningAppProcesses) { Log.i(TAG, "Audio focus lost");
// Check the importance of the running app process
if (processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
// This app is in the foreground, which might have caused the loss of audio focus
Log.i("AudioFocus", "App ${processInfo.processName} might have caused the loss of audio focus")
}
}
} }
} }
} catch(ex: Throwable) { } catch(ex: Throwable) {

@ -1 +1 @@
Subproject commit 850acb49a8d988f45b893ef848d559dd74d0db1f Subproject commit 31490e10f9ef661d6365c0cf4d0fcedf9d807a69

@ -1 +1 @@
Subproject commit 850acb49a8d988f45b893ef848d559dd74d0db1f Subproject commit 31490e10f9ef661d6365c0cf4d0fcedf9d807a69