11package com.pira.ccloud
22
3+ import android.app.Activity
34import android.content.Context
45import android.content.Intent
56import android.content.pm.ActivityInfo
67import android.graphics.Typeface
8+ import android.media.AudioManager
79import android.net.Uri
810import android.os.Build
911import android.os.Bundle
1012import android.os.Handler
1113import android.os.Looper
14+ import android.provider.Settings
1215import android.util.TypedValue
1316import android.view.GestureDetector
1417import android.view.MotionEvent
@@ -17,10 +20,12 @@ import android.view.ViewGroup
1720import android.view.WindowInsets
1821import android.view.WindowInsetsController
1922import android.widget.FrameLayout
23+ import kotlin.math.abs
2024import androidx.activity.ComponentActivity
2125import androidx.activity.compose.setContent
2226import androidx.compose.foundation.background
2327import androidx.compose.foundation.clickable
28+ import androidx.compose.foundation.gestures.detectDragGestures
2429import androidx.compose.foundation.gestures.detectTapGestures
2530import androidx.compose.foundation.layout.Box
2631import androidx.compose.foundation.layout.Column
@@ -34,11 +39,13 @@ import androidx.compose.foundation.layout.width
3439import androidx.compose.foundation.shape.RoundedCornerShape
3540import androidx.compose.material.icons.Icons
3641import androidx.compose.material.icons.automirrored.filled.ArrowBack
42+ import androidx.compose.material.icons.filled.BrightnessMedium
3743import androidx.compose.material.icons.filled.Forward
3844import androidx.compose.material.icons.filled.Pause
3945import androidx.compose.material.icons.filled.PlayArrow
4046import androidx.compose.material.icons.filled.Replay
4147import androidx.compose.material.icons.filled.Speed
48+ import androidx.compose.material.icons.filled.VolumeUp
4249import androidx.compose.material3.DropdownMenu
4350import androidx.compose.material3.DropdownMenuItem
4451import androidx.compose.material3.Icon
@@ -109,6 +116,7 @@ fun PlayerView.setSubtitleColors(settings: SubtitleSettings, typeface: Typeface?
109116class VideoPlayerActivity : ComponentActivity () {
110117 companion object {
111118 const val EXTRA_VIDEO_URL = " video_url"
119+ const val REQUEST_WRITE_SETTINGS = 1001
112120
113121 fun start (context : Context , videoUrl : String ) {
114122 val intent = Intent (context, VideoPlayerActivity ::class .java).apply {
@@ -133,6 +141,9 @@ class VideoPlayerActivity : ComponentActivity() {
133141 // Keep screen on while in video player
134142 window.addFlags(android.view.WindowManager .LayoutParams .FLAG_KEEP_SCREEN_ON )
135143
144+ // Check and request brightness control permission if needed
145+ checkAndRequestBrightnessPermission()
146+
136147 videoUrl = intent.getStringExtra(EXTRA_VIDEO_URL )
137148
138149 if (videoUrl != null ) {
@@ -146,6 +157,28 @@ class VideoPlayerActivity : ComponentActivity() {
146157 }
147158 }
148159
160+ private fun checkAndRequestBrightnessPermission () {
161+ // Check if we have permission to write system settings
162+ if (! Settings .System .canWrite(this )) {
163+ // Request permission to write system settings
164+ val intent = Intent (Settings .ACTION_MANAGE_WRITE_SETTINGS )
165+ intent.data = Uri .parse(" package:${packageName} " )
166+ startActivityForResult(intent, REQUEST_WRITE_SETTINGS )
167+ }
168+ }
169+
170+ override fun onActivityResult (requestCode : Int , resultCode : Int , data : Intent ? ) {
171+ super .onActivityResult(requestCode, resultCode, data)
172+ if (requestCode == REQUEST_WRITE_SETTINGS ) {
173+ // Permission result for writing system settings
174+ if (Settings .System .canWrite(this )) {
175+ // Permission granted
176+ } else {
177+ // Permission denied - we'll work with window-level brightness only
178+ }
179+ }
180+ }
181+
149182 // Handle TV remote control key events
150183 override fun onKeyDown (keyCode : Int , event : android.view.KeyEvent ? ): Boolean {
151184 exoPlayer?.let { player ->
@@ -256,6 +289,12 @@ fun VideoPlayerScreen(
256289 var playbackSpeed by remember { mutableStateOf(1.0f ) }
257290 var showSpeedDropdown by remember { mutableStateOf(false ) }
258291
292+ // Brightness and volume control states
293+ var showBrightnessIndicator by remember { mutableStateOf(false ) }
294+ var showVolumeIndicator by remember { mutableStateOf(false ) }
295+ var brightnessLevel by remember { mutableStateOf(0f ) }
296+ var volumeLevel by remember { mutableStateOf(0f ) }
297+
259298 // Predefined playback speed options
260299 val speedOptions = remember {
261300 listOf (0.25f , 0.5f , 0.75f , 1.0f , 1.25f , 1.5f , 1.75f , 2.0f , 2.5f , 3.0f , 3.5f )
@@ -522,6 +561,150 @@ fun VideoPlayerScreen(
522561 }
523562 )
524563 }
564+ // Add swipe gesture detection for brightness and volume control
565+ .pointerInput(Unit ) {
566+ var initialX = 0f
567+ var initialY = 0f
568+ var isTracking = false
569+ var trackingSide: String? = null // "left" for brightness, "right" for volume
570+ var initialBrightness = 0f
571+ var initialVolume = 0f
572+
573+ detectDragGestures(
574+ onDragStart = { offset ->
575+ initialX = offset.x
576+ initialY = offset.y
577+ isTracking = true
578+
579+ // Determine which side of the screen the gesture started on
580+ val screenWidth = size.width
581+ if (initialX < screenWidth * 0.5f ) {
582+ trackingSide = " left" // Left side for brightness
583+ } else {
584+ trackingSide = " right" // Right side for volume
585+ }
586+
587+ // Get initial brightness and volume levels
588+ when (trackingSide) {
589+ " left" -> {
590+ val window = (context as Activity ).window
591+ val layoutParams = window.attributes
592+ initialBrightness = layoutParams.screenBrightness
593+ if (initialBrightness < 0 ) {
594+ // If brightness is set to system default, get the current system brightness
595+ try {
596+ val brightnessMode = Settings .System .getInt(
597+ context.contentResolver,
598+ Settings .System .SCREEN_BRIGHTNESS_MODE
599+ )
600+ if (brightnessMode == Settings .System .SCREEN_BRIGHTNESS_MODE_AUTOMATIC ) {
601+ // For automatic brightness, we'll use a default value
602+ initialBrightness = 0.5f // Default to 50%
603+ } else {
604+ val systemBrightness = Settings .System .getInt(
605+ context.contentResolver,
606+ Settings .System .SCREEN_BRIGHTNESS
607+ )
608+ initialBrightness = systemBrightness / 255f
609+ }
610+ } catch (e: Exception ) {
611+ initialBrightness = 0.5f // Default to 50%
612+ }
613+ }
614+ brightnessLevel = initialBrightness
615+ showBrightnessIndicator = true
616+ }
617+ " right" -> {
618+ val audioManager = context.getSystemService(Context .AUDIO_SERVICE ) as AudioManager
619+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager .STREAM_MUSIC )
620+ val currentVolume = audioManager.getStreamVolume(AudioManager .STREAM_MUSIC )
621+ initialVolume = currentVolume.toFloat()
622+ volumeLevel = initialVolume / maxVolume.toFloat()
623+ showVolumeIndicator = true
624+ }
625+ }
626+ },
627+ onDragEnd = {
628+ isTracking = false
629+ trackingSide = null
630+
631+ // Hide indicators after a delay
632+ CoroutineScope (Dispatchers .Main ).launch {
633+ delay(1000 )
634+ showBrightnessIndicator = false
635+ showVolumeIndicator = false
636+ }
637+ },
638+ onDragCancel = {
639+ isTracking = false
640+ trackingSide = null
641+ showBrightnessIndicator = false
642+ showVolumeIndicator = false
643+ },
644+ onDrag = { change, dragAmount ->
645+ if (! isTracking) return @detectDragGestures
646+
647+ val dragY = dragAmount.y
648+
649+ // Only process vertical drag gestures
650+ if (abs(dragY) > abs(change.position.x - initialX) * 0.5f ) {
651+ when (trackingSide) {
652+ " left" -> {
653+ // Adjust brightness based on vertical drag (up = increase, down = decrease)
654+ val delta = - dragY * 0.01f // Invert Y axis (up is negative)
655+ brightnessLevel = (initialBrightness + delta).coerceIn(0f , 1f )
656+
657+ try {
658+ // Apply brightness change to the current window
659+ val window = (context as Activity ).window
660+ val layoutParams = window.attributes
661+ layoutParams.screenBrightness = brightnessLevel
662+ window.attributes = layoutParams
663+
664+ // Also try to change system brightness if we have permission
665+ if (Settings .System .canWrite(context)) {
666+ Settings .System .putInt(
667+ context.contentResolver,
668+ Settings .System .SCREEN_BRIGHTNESS ,
669+ (brightnessLevel * 255 ).toInt()
670+ )
671+ }
672+
673+ // Update UI indicator
674+ showBrightnessIndicator = true
675+ } catch (e: Exception ) {
676+ // Ignore permission or other errors
677+ }
678+ }
679+ " right" -> {
680+ // Adjust volume based on vertical drag (up = increase, down = decrease)
681+ val audioManager = context.getSystemService(Context .AUDIO_SERVICE ) as AudioManager
682+ val maxVolume = audioManager.getStreamMaxVolume(AudioManager .STREAM_MUSIC )
683+ val delta = - dragY * 0.01f * maxVolume // Invert Y axis (up is negative)
684+ val newVolume = (initialVolume + delta).coerceIn(0f , maxVolume.toFloat())
685+
686+ try {
687+ // Apply volume change
688+ audioManager.setStreamVolume(
689+ AudioManager .STREAM_MUSIC ,
690+ newVolume.toInt(),
691+ 0 // No flags
692+ )
693+
694+ // Update volume level for UI indicator
695+ volumeLevel = newVolume / maxVolume.toFloat()
696+
697+ // Update UI indicator
698+ showVolumeIndicator = true
699+ } catch (e: Exception ) {
700+ // Ignore permission or other errors
701+ }
702+ }
703+ }
704+ }
705+ }
706+ )
707+ }
525708 ) {
526709 // Show error message if player failed to initialize
527710 if (playerError != null ) {
@@ -653,6 +836,64 @@ fun VideoPlayerScreen(
653836 }
654837 }
655838
839+ // Brightness indicator
840+ if (showBrightnessIndicator) {
841+ Box (
842+ modifier = Modifier
843+ .fillMaxSize()
844+ .padding(32 .dp),
845+ contentAlignment = Alignment .CenterStart
846+ ) {
847+ Column (
848+ horizontalAlignment = Alignment .CenterHorizontally ,
849+ modifier = Modifier
850+ .background(Color .Black .copy(alpha = 0.7f ), RoundedCornerShape (8 .dp))
851+ .padding(16 .dp)
852+ ) {
853+ Icon (
854+ imageVector = Icons .Default .BrightnessMedium ,
855+ contentDescription = " Brightness" ,
856+ tint = Color .White ,
857+ modifier = Modifier .size(48 .dp)
858+ )
859+ Text (
860+ text = " ${(brightnessLevel * 100 ).toInt()} %" ,
861+ color = Color .White ,
862+ modifier = Modifier .padding(top = 8 .dp)
863+ )
864+ }
865+ }
866+ }
867+
868+ // Volume indicator
869+ if (showVolumeIndicator) {
870+ Box (
871+ modifier = Modifier
872+ .fillMaxSize()
873+ .padding(32 .dp),
874+ contentAlignment = Alignment .CenterEnd
875+ ) {
876+ Column (
877+ horizontalAlignment = Alignment .CenterHorizontally ,
878+ modifier = Modifier
879+ .background(Color .Black .copy(alpha = 0.7f ), RoundedCornerShape (8 .dp))
880+ .padding(16 .dp)
881+ ) {
882+ Icon (
883+ imageVector = Icons .Default .VolumeUp ,
884+ contentDescription = " Volume" ,
885+ tint = Color .White ,
886+ modifier = Modifier .size(48 .dp)
887+ )
888+ Text (
889+ text = " ${(volumeLevel * 100 ).toInt()} %" ,
890+ color = Color .White ,
891+ modifier = Modifier .padding(top = 8 .dp)
892+ )
893+ }
894+ }
895+ }
896+
656897 // Custom controls overlay
657898 if (showControls) {
658899 Column (
0 commit comments