diff --git a/app/build.gradle b/app/build.gradle
index 80b8a5f..17eccd4 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,12 +4,12 @@ plugins {
}
android {
- compileSdk 34
+ compileSdk 35
defaultConfig {
applicationId "com.halil.ozel.exoplayerscreenlock"
minSdk 24
- targetSdk 34
+ targetSdk 35
versionCode 1
versionName "1.0"
@@ -41,6 +41,8 @@ dependencies {
implementation 'com.google.android.material:material:1.10.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
- // ExoPlayer
- implementation 'com.google.android.exoplayer:exoplayer:2.19.1'
-}
\ No newline at end of file
+ // Media3 (modern ExoPlayer)
+ implementation 'androidx.media3:media3-exoplayer:1.8.1'
+ implementation 'androidx.media3:media3-exoplayer-hls:1.8.1'
+ implementation 'androidx.media3:media3-ui:1.8.1'
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 04a0277..5069f8e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,10 +1,9 @@
-
+
@@ -24,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/halil/ozel/exoplayerscreenlock/MainActivity.kt b/app/src/main/java/com/halil/ozel/exoplayerscreenlock/MainActivity.kt
index accfdeb..c3f92d4 100644
--- a/app/src/main/java/com/halil/ozel/exoplayerscreenlock/MainActivity.kt
+++ b/app/src/main/java/com/halil/ozel/exoplayerscreenlock/MainActivity.kt
@@ -7,152 +7,228 @@ import android.os.Bundle
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout
+import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
-import com.google.android.exoplayer2.ExoPlayer
-import com.google.android.exoplayer2.MediaItem
-import com.google.android.exoplayer2.source.hls.HlsMediaSource
-import com.google.android.exoplayer2.upstream.DefaultHttpDataSource
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.WindowInsetsControllerCompat
+import androidx.media3.common.MediaItem
+import androidx.media3.exoplayer.ExoPlayer
import com.halil.ozel.exoplayerscreenlock.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
- private var playbackPosition = 0L
-
private lateinit var binding: ActivityMainBinding
- private var exoPlayer: ExoPlayer? = null
- private lateinit var imageViewFullScreen: ImageView
- private lateinit var imageViewLock: ImageView
- private lateinit var linearLayoutControlUp: LinearLayout
- private lateinit var linearLayoutControlBottom: LinearLayout
+ private var player: ExoPlayer? = null
+
+ private lateinit var fullScreenButton: ImageView
+ private lateinit var lockButton: ImageView
+ private lateinit var topControls: LinearLayout
+ private lateinit var bottomControls: LinearLayout
+
+ private var playbackPosition = 0L
+ private var playWhenReady = true
+ private var isFullScreen = false
+ private var isLocked = false
- @SuppressLint("SourceLockedOrientationActivity")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+ restorePlayerState(savedInstanceState)
+ configureEdgeToEdgePlayer()
setView()
- preparePlayer()
- setFindViewById()
- setLockScreen()
- setFullScreen()
+ bindControllerViews()
+ setupControls()
+ setupBackNavigation()
+ initializePlayer()
+ }
+
+ override fun onStart() {
+ super.onStart()
+ if (player == null) initializePlayer()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (player == null) initializePlayer()
+ updateSystemBars()
+ player?.playWhenReady = playWhenReady
+ }
+
+ override fun onPause() {
+ savePlaybackState()
+ player?.pause()
+ super.onPause()
+ }
+
+ override fun onStop() {
+ savePlaybackState()
+ releasePlayer()
+ super.onStop()
+ }
+
+ override fun onDestroy() {
+ releasePlayer()
+ super.onDestroy()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ savePlaybackState()
+ outState.putLong(KEY_PLAYBACK_POSITION, playbackPosition)
+ outState.putBoolean(KEY_PLAY_WHEN_READY, playWhenReady)
+ outState.putBoolean(KEY_IS_FULLSCREEN, isFullScreen)
+ outState.putBoolean(KEY_IS_LOCKED, isLocked)
+ super.onSaveInstanceState(outState)
}
private fun setView() {
binding = ActivityMainBinding.inflate(layoutInflater)
- val view = binding.root
- setContentView(view)
+ setContentView(binding.root)
}
- private fun setFindViewById() {
- imageViewFullScreen = findViewById(R.id.imageViewFullScreen)
- imageViewLock = findViewById(R.id.imageViewLock)
- linearLayoutControlUp = findViewById(R.id.linearLayoutControlUp)
- linearLayoutControlBottom = findViewById(R.id.linearLayoutControlBottom)
+ private fun configureEdgeToEdgePlayer() {
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+ WindowCompat.getInsetsController(window, window.decorView).systemBarsBehavior =
+ WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
}
- private fun preparePlayer() {
- exoPlayer = ExoPlayer.Builder(this).setSeekBackIncrementMs(INCREMENT_MILLIS)
- .setSeekForwardIncrementMs(INCREMENT_MILLIS)
- .build()
- exoPlayer?.playWhenReady = true
- binding.player.player = exoPlayer
- val defaultHttpDataSourceFactory = DefaultHttpDataSource.Factory()
- val mediaItem =
- MediaItem.fromUri(URL)
- val mediaSource =
- HlsMediaSource.Factory(defaultHttpDataSourceFactory).createMediaSource(mediaItem)
- exoPlayer?.apply {
- setMediaSource(mediaSource)
- seekTo(playbackPosition)
- playWhenReady = playWhenReady
- prepare()
- }
+ private fun bindControllerViews() {
+ fullScreenButton = findViewById(R.id.imageViewFullScreen)
+ lockButton = findViewById(R.id.imageViewLock)
+ topControls = findViewById(R.id.linearLayoutControlUp)
+ bottomControls = findViewById(R.id.linearLayoutControlBottom)
}
+ private fun setupControls() {
+ updateLockState()
+ updateFullScreenState()
- private fun lockScreen(lock: Boolean) {
- if (lock) {
- linearLayoutControlUp.visibility = View.INVISIBLE
- linearLayoutControlBottom.visibility = View.INVISIBLE
- } else {
- linearLayoutControlUp.visibility = View.VISIBLE
- linearLayoutControlBottom.visibility = View.VISIBLE
+ lockButton.setOnClickListener {
+ isLocked = !isLocked
+ updateLockState()
+ }
+
+ fullScreenButton.setOnClickListener {
+ toggleFullScreen()
}
}
- private fun setLockScreen() {
- imageViewLock.setOnClickListener {
- if (!isLock) {
- imageViewLock.setImageDrawable(
- ContextCompat.getDrawable(
- applicationContext,
- R.drawable.ic_baseline_lock
- )
- )
- } else {
- imageViewLock.setImageDrawable(
- ContextCompat.getDrawable(
- applicationContext,
- R.drawable.ic_baseline_lock_open
- )
- )
+ private fun setupBackNavigation() {
+ onBackPressedDispatcher.addCallback(
+ this,
+ object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ when {
+ isLocked -> Unit
+ isFullScreen || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE ->
+ exitFullScreen()
+ else -> {
+ isEnabled = false
+ onBackPressedDispatcher.onBackPressed()
+ }
+ }
+ }
}
- isLock = !isLock
- lockScreen(isLock)
- }
+ )
}
- @SuppressLint("SourceLockedOrientationActivity")
- private fun setFullScreen() {
- imageViewFullScreen.setOnClickListener {
- if (!isFullScreen) {
- imageViewFullScreen.setImageDrawable(
- ContextCompat.getDrawable(
- applicationContext,
- R.drawable.ic_baseline_fullscreen_exit
- )
- )
- requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
- } else {
- imageViewFullScreen.setImageDrawable(
- ContextCompat.getDrawable(
- applicationContext,
- R.drawable.ic_baseline_fullscreen
- )
- )
- requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+ private fun restorePlayerState(savedInstanceState: Bundle?) {
+ savedInstanceState ?: return
+ playbackPosition = savedInstanceState.getLong(KEY_PLAYBACK_POSITION, 0L)
+ playWhenReady = savedInstanceState.getBoolean(KEY_PLAY_WHEN_READY, true)
+ isFullScreen = savedInstanceState.getBoolean(KEY_IS_FULLSCREEN, false)
+ isLocked = savedInstanceState.getBoolean(KEY_IS_LOCKED, false)
+ }
+
+ private fun initializePlayer() {
+ if (player != null) return
+
+ player = ExoPlayer.Builder(this)
+ .setSeekBackIncrementMs(SEEK_INCREMENT_MILLIS)
+ .setSeekForwardIncrementMs(SEEK_INCREMENT_MILLIS)
+ .build()
+ .also { exoPlayer ->
+ binding.player.player = exoPlayer
+ exoPlayer.setMediaItem(MediaItem.fromUri(STREAM_URL))
+ exoPlayer.seekTo(playbackPosition)
+ exoPlayer.playWhenReady = playWhenReady
+ exoPlayer.prepare()
}
- isFullScreen = !isFullScreen
+ }
+
+ private fun savePlaybackState() {
+ player?.let { exoPlayer ->
+ playbackPosition = exoPlayer.currentPosition.coerceAtLeast(0L)
+ playWhenReady = exoPlayer.playWhenReady
}
}
- @Deprecated("Deprecated in Java")
- override fun onBackPressed() {
- if (isLock) return
- if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
- imageViewFullScreen.performClick()
- } else super.onBackPressed()
+ private fun releasePlayer() {
+ savePlaybackState()
+ binding.player.player = null
+ player?.release()
+ player = null
}
+ private fun updateLockState() {
+ val controlsVisibility = if (isLocked) View.INVISIBLE else View.VISIBLE
+ topControls.visibility = controlsVisibility
+ bottomControls.visibility = controlsVisibility
+ lockButton.setImageResource(
+ if (isLocked) R.drawable.ic_baseline_lock else R.drawable.ic_baseline_lock_open
+ )
+ lockButton.contentDescription = getString(
+ if (isLocked) R.string.unlock_controls else R.string.lock_controls
+ )
+ }
- override fun onStop() {
- super.onStop()
- exoPlayer?.stop()
+ @SuppressLint("SourceLockedOrientationActivity")
+ private fun toggleFullScreen() {
+ if (isFullScreen) {
+ exitFullScreen()
+ } else {
+ isFullScreen = true
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
+ updateFullScreenState()
+ }
}
- override fun onDestroy() {
- super.onDestroy()
- exoPlayer?.release()
+ @SuppressLint("SourceLockedOrientationActivity")
+ private fun exitFullScreen() {
+ isFullScreen = false
+ requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+ updateFullScreenState()
}
- override fun onPause() {
- super.onPause()
- exoPlayer?.pause()
+ private fun updateFullScreenState() {
+ fullScreenButton.setImageDrawable(
+ ContextCompat.getDrawable(
+ this,
+ if (isFullScreen) R.drawable.ic_baseline_fullscreen_exit else R.drawable.ic_baseline_fullscreen
+ )
+ )
+ fullScreenButton.contentDescription = getString(
+ if (isFullScreen) R.string.exit_full_screen else R.string.enter_full_screen
+ )
+ updateSystemBars()
+ }
+
+ private fun updateSystemBars() {
+ val controller = WindowCompat.getInsetsController(window, window.decorView)
+ if (isFullScreen || resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ controller.hide(WindowInsetsCompat.Type.systemBars())
+ } else {
+ controller.show(WindowInsetsCompat.Type.systemBars())
+ }
}
companion object {
- private const val URL = "https://d1gnaphp93fop2.cloudfront.net/videos/multiresolution/rendition_new10.m3u8"
- private var isFullScreen = false
- private var isLock = false
- private const val INCREMENT_MILLIS = 5000L
+ private const val STREAM_URL =
+ "https://d1gnaphp93fop2.cloudfront.net/videos/multiresolution/rendition_new10.m3u8"
+ private const val SEEK_INCREMENT_MILLIS = 5_000L
+ private const val KEY_PLAYBACK_POSITION = "playback_position"
+ private const val KEY_PLAY_WHEN_READY = "play_when_ready"
+ private const val KEY_IS_FULLSCREEN = "is_fullscreen"
+ private const val KEY_IS_LOCKED = "is_locked"
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/drawable/bg_bottom_panel.xml b/app/src/main/res/drawable/bg_bottom_panel.xml
new file mode 100644
index 0000000..24b8c45
--- /dev/null
+++ b/app/src/main/res/drawable/bg_bottom_panel.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_control_pill.xml b/app/src/main/res/drawable/bg_control_pill.xml
new file mode 100644
index 0000000..96b3a4d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_control_pill.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_player_overlay.xml b/app/src/main/res/drawable/bg_player_overlay.xml
new file mode 100644
index 0000000..15b6a61
--- /dev/null
+++ b/app/src/main/res/drawable/bg_player_overlay.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_primary_control.xml b/app/src/main/res/drawable/bg_primary_control.xml
new file mode 100644
index 0000000..080c864
--- /dev/null
+++ b/app/src/main/res/drawable/bg_primary_control.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 4c111ab..747340d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,15 +1,24 @@
-
+ android:layout_height="match_parent"
+ android:background="@color/player_surface">
-
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/layout/custom_controller.xml b/app/src/main/res/layout/custom_controller.xml
index 52a19d4..083d74f 100644
--- a/app/src/main/res/layout/custom_controller.xml
+++ b/app/src/main/res/layout/custom_controller.xml
@@ -1,103 +1,142 @@
-
+ android:background="@drawable/bg_player_overlay"
+ android:paddingStart="20dp"
+ android:paddingTop="20dp"
+ android:paddingEnd="20dp"
+ android:paddingBottom="20dp">
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
-
-
+ android:paddingStart="14dp"
+ android:paddingTop="10dp"
+ android:paddingEnd="14dp"
+ android:paddingBottom="10dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent">
+
+ android:id="@id/exo_rew"
+ style="@style/PlayerControlButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@string/rewind"
+ android:src="@drawable/ic_baseline_replay" />
+
+
+
+
+
+
+
+
+ android:id="@id/exo_ffwd"
+ style="@style/PlayerControlButton"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:contentDescription="@string/fast_forward"
+ android:src="@drawable/ic_baseline_forward" />
+ android:paddingStart="16dp"
+ android:paddingTop="12dp"
+ android:paddingEnd="16dp"
+ android:paddingBottom="14dp"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent">
+
+ android:gravity="center_vertical"
+ android:orientation="horizontal">
+
+ android:layout_height="wrap_content" />
+
+ android:layout_marginStart="4dp"
+ android:layout_marginEnd="4dp"
+ android:text="@string/time_separator" />
+
+ android:layout_weight="1" />
+
+ style="@style/PlayerControlButton"
+ android:layout_width="44dp"
+ android:layout_height="44dp"
+ android:contentDescription="@string/enter_full_screen"
+ android:src="@drawable/ic_baseline_fullscreen" />
-
+ android:layout_height="32dp"
+ android:layout_marginTop="8dp"
+ app:bar_height="4dp"
+ app:buffered_color="@color/player_buffered"
+ app:played_color="@color/player_accent"
+ app:scrubber_color="@color/player_accent"
+ app:scrubber_dragged_size="18dp"
+ app:scrubber_enabled_size="14dp"
+ app:unplayed_color="@color/player_unplayed" />
-
\ No newline at end of file
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index 580ad18..06d37c8 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,16 +1,17 @@
-
-
-
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..fff3871 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,10 +1,17 @@
- #FFBB86FC
- #FF6200EE
- #FF3700B3
- #FF03DAC5
- #FF018786
#FF000000
#FFFFFFFF
-
\ No newline at end of file
+
+ #05070D
+ #FF3D71
+ #FFFFFFFF
+ #F7F8FA
+ #B9C0CC
+ #B3121722
+ #26FFFFFF
+ #A6000000
+ #33000000
+ #8A9AA8B5
+ #595F6B78
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d6f521d..33a465c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,12 @@
- ExoPlayerScreenLock
-
\ No newline at end of file
+ ExoPlayer Screen Lock
+ Tam ekrana geç
+ Tam ekrandan çık
+ Kontrolleri kilitle
+ Kontrollerin kilidini aç
+ Oynat
+ Duraklat
+ 5 saniye geri sar
+ 5 saniye ileri sar
+ /
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 96608e4..d5e6f0b 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -2,7 +2,30 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 203fa1b..06d37c8 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,22 +1,17 @@
-
-
-
-
-
\ No newline at end of file
+
diff --git a/gradle.properties b/gradle.properties
index 022338b..cccbfe6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -22,4 +22,4 @@ kotlin.code.style=official
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.defaults.buildfeatures.buildconfig=true
-android.nonFinalResIds=false
\ No newline at end of file
+android.nonFinalResIds=false