Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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'
}
// 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'
}
8 changes: 3 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.INTERNET" />

<application
android:usesCleartextTraffic="true"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ExoPlayerScreenLock">
<activity
android:theme="@style/Theme.AppCompat.Light.NoActionBar.FullScreen"
android:configChanges="orientation|screenSize|screenLayout"
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -24,4 +22,4 @@
</activity>
</application>

</manifest>
</manifest>
288 changes: 182 additions & 106 deletions app/src/main/java/com/halil/ozel/exoplayerscreenlock/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
}
Loading
Loading