diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 65c64e538..2d3f8ed74 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -121,6 +121,12 @@
+
+
+
+
diff --git a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
index 088d51553..2d62e9e34 100644
--- a/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
+++ b/course/src/main/java/org/openedx/course/presentation/unit/video/YoutubeVideoUnitFragment.kt
@@ -56,6 +56,11 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
private var blockId = ""
+ // Track if we're using WebView fallback (persists across rotations)
+ private var isUsingWebViewFallback = false
+
+ // Track last known WebView playback position (persists across rotations)
+ private var webViewLastPlaybackPosition: Float = 0f
private val youtubeTrackerListener = YouTubePlayerTracker()
@@ -72,6 +77,12 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
blockId = getString(ARG_BLOCK_ID, "")
}
viewModel.downloadSubtitles()
+
+ savedInstanceState?.let {
+ isUsingWebViewFallback = it.getBoolean(KEY_USING_WEBVIEW_FALLBACK, false)
+ webViewLastPlaybackPosition = it.getFloat(KEY_WEBVIEW_PLAYBACK_POSITION, 0f)
+ android.util.Log.d("YoutubeVideoUnit", "Restored state - isUsingWebViewFallback: $isUsingWebViewFallback, playback position: $webViewLastPlaybackPosition sec")
+ }
}
override fun onCreateView(
@@ -141,12 +152,19 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
binding.connectionError.isVisible = !viewModel.hasInternetConnection
- // Hide WebView initially - it's only a fallback
- binding.fallbackWebview?.visibility = View.GONE
- binding.youtubePlayerView.visibility = View.VISIBLE
-
- // Initialize YouTube player library (original approach)
- initializeYoutubePlayer()
+ if (isUsingWebViewFallback) {
+ // Hide YouTube player, show WebView
+ binding.youtubePlayerView.visibility = View.GONE
+ binding.fallbackWebview.visibility = View.VISIBLE
+ // Re-setup WebView
+ setupWebViewPlayer()
+ } else {
+ // Hide WebView initially - it's only a fallback
+ binding.fallbackWebview.visibility = View.GONE
+ binding.youtubePlayerView.visibility = View.VISIBLE
+ // Initialize YouTube player library (original approach)
+ initializeYoutubePlayer()
+ }
}
private fun initializeYoutubePlayer() {
@@ -258,11 +276,11 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
}
private fun switchToWebViewFallback() {
- android.util.Log.w("YoutubePlayer", "Switching to WebView fallback")
+ isUsingWebViewFallback = true
// Hide YouTube player, show WebView
binding.youtubePlayerView.visibility = View.GONE
- binding.fallbackWebview?.visibility = View.VISIBLE
+ binding.fallbackWebview.visibility = View.VISIBLE
// Setup and load WebView
setupWebViewPlayer()
@@ -273,6 +291,30 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
_youTubePlayer?.pause()
}
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ // Save state before rotation
+ outState.putBoolean(KEY_USING_WEBVIEW_FALLBACK, isUsingWebViewFallback)
+
+ // If using WebView, get current playback position via JavaScript
+ if (isUsingWebViewFallback && _binding != null) {
+ binding.fallbackWebview?.evaluateJavascript(
+ "(function() { try { return player ? player.getCurrentTime() : 0; } catch(e) { return 0; } })()"
+ ) { result ->
+ try {
+ val position = result?.toFloatOrNull() ?: 0f
+ webViewLastPlaybackPosition = position
+ android.util.Log.d("YoutubeVideoUnit", "Saved WebView playback position: $position sec")
+ } catch (e: Exception) {
+ android.util.Log.e("YoutubeVideoUnit", "Error saving playback position", e)
+ }
+ }
+ }
+
+ outState.putFloat(KEY_WEBVIEW_PLAYBACK_POSITION, webViewLastPlaybackPosition)
+ android.util.Log.d("YoutubeVideoUnit", "Saving state - isUsingWebViewFallback: $isUsingWebViewFallback, position: $webViewLastPlaybackPosition sec")
+ }
+
override fun onDestroyView() {
_youTubePlayer = null
super.onDestroyView()
@@ -281,19 +323,16 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
private fun setupWebViewPlayer() {
val videoId = extractYouTubeVideoId(viewModel.videoUrl) ?: run {
- android.util.Log.e("YoutubePlayer", "Failed to extract video ID from: ${viewModel.videoUrl}")
+ android.util.Log.e("YoutubeVideoUnit", "Failed to extract video ID from: ${viewModel.videoUrl}")
return
}
- android.util.Log.d("YoutubePlayer", "Setting up WebView for video: $videoId")
- android.util.Log.d("YoutubePlayer", "Full URL: ${viewModel.videoUrl}")
-
// Hide YouTube player library view, show WebView
binding.youtubePlayerView.visibility = View.GONE
- binding.fallbackWebview?.visibility = View.VISIBLE
+ binding.fallbackWebview.visibility = View.VISIBLE
// Configure WebView with better settings for video playback
- binding.fallbackWebview?.apply {
+ binding.fallbackWebview.apply {
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.mediaPlaybackRequiresUserGesture = false
@@ -312,6 +351,9 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
// Set black background like native player
setBackgroundColor(android.graphics.Color.BLACK)
+ // Add JavaScript interface to track playback position
+ addJavascriptInterface(WebAppInterface(), "Android")
+
// Add WebChromeClient for fullscreen support
webChromeClient = object : WebChromeClient() {
private var customView: View? = null
@@ -336,10 +378,10 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
} else {
@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
- )
+ View.SYSTEM_UI_FLAG_FULLSCREEN
+ or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+ )
}
val contentView = activity.findViewById(android.R.id.content)
@@ -395,12 +437,12 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
// Check if it's trying to navigate to YouTube website, channel, or external links
when {
url.contains("youtube.com/channel") ||
- url.contains("youtube.com/user") ||
- url.contains("youtube.com/c/") ||
- url.contains("youtube.com/@") ||
- url.contains("youtube.com/watch") ||
- url.contains("youtube.com/playlist") ||
- url.contains("youtu.be") -> {
+ url.contains("youtube.com/user") ||
+ url.contains("youtube.com/c/") ||
+ url.contains("youtube.com/@") ||
+ url.contains("youtube.com/watch") ||
+ url.contains("youtube.com/playlist") ||
+ url.contains("youtu.be") -> {
android.util.Log.w("YoutubePlayer", "Blocked YouTube navigation attempt")
return true // Block navigation
}
@@ -424,12 +466,12 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
when {
url.contains("youtube.com/channel") ||
- url.contains("youtube.com/user") ||
- url.contains("youtube.com/c/") ||
- url.contains("youtube.com/@") ||
- url.contains("youtube.com/watch") ||
- url.contains("youtube.com/playlist") ||
- url.contains("youtu.be") -> {
+ url.contains("youtube.com/user") ||
+ url.contains("youtube.com/c/") ||
+ url.contains("youtube.com/@") ||
+ url.contains("youtube.com/watch") ||
+ url.contains("youtube.com/playlist") ||
+ url.contains("youtu.be") -> {
android.util.Log.w("YoutubePlayer", "Blocked YouTube navigation attempt")
return true
}
@@ -449,8 +491,15 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)
}
private fun loadYouTubeEmbed(videoId: String) {
- // Calculate start time in seconds
- val startTime = (viewModel.getCurrentVideoTime() / 1000).toInt()
+ // Use restored position if available (after rotation), otherwise use ViewModel's position
+ val startTime = if (webViewLastPlaybackPosition > 0) {
+ android.util.Log.d("YoutubeVideoUnit", "Using restored position: $webViewLastPlaybackPosition sec")
+ webViewLastPlaybackPosition.toInt()
+ } else {
+ val vmTime = (viewModel.getCurrentVideoTime() / 1000).toInt()
+ android.util.Log.d("YoutubeVideoUnit", "Using ViewModel position: $vmTime sec")
+ vmTime
+ }
// Create HTML with YouTube IFrame Player API for native-like experience
val embedHtml = """
@@ -519,6 +568,7 @@ class YoutubeVideoUnitFragment : Fragment(R.layout.fragment_youtube_video_unit)